Repository: arkivanov/Decompose
Pull request #878 (2025-05-26 18:13:49)

Added Child Items navigation model


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a flexible API for managing collections of child components ("router items") with fine-grained lifecycle control, state saving, and navigation.
  - Added support for lazy instantiation and lifecycle management of child items, including new abstractions for navigation, state saving/restoration, and item lifecycle states.
  - Provided Compose integration for controlling child item lifecycles in lazy lists and grids, enabling efficient resource management and preloading.
  - Updated sample gallery and thumbnail components to use the new router items API for dynamic, lifecycle-aware image grids.
  - Added priority support to child back handlers for improved back navigation control.
  - Added new test utilities and classes for lifecycle and back handling verification.
  - Introduced a new child controller for lifecycle-aware management of keyed child components.
  - Added transient navigation state saver to preserve navigation state across configuration changes without serialization.
  - Added extension functions and factories to simplify creation and navigation of child items.
  - Added comprehensive test suites covering child controller, lazy items lifecycle, back button handling, retained instances, and saved state restoration.
  - Added a new `SimpleLazyChildItems` implementation for simplified lazy child item management.
  - Introduced a `TestLazyChildItems` test utility with lifecycle state assertions and component tracking.
  - Added lifecycle event order tests for child stacks in integration scenarios.

- **Bug Fixes**
  - Improved back handler priority and lifecycle management for child components.

- **Documentation**
  - Added comprehensive documentation and usage examples for new router items API and Compose extensions.

- **Tests**
  - Introduced extensive test coverage for router items, including lifecycle, state retention, back handling, and Compose integration.
  - Added tests verifying lazy item lifecycle states, retained instances, saved state restoration, and back button handling.
  - Added tests ensuring correct back callback precedence between context and child registrations.

- **Refactor**
  - Refactored sample gallery logic to leverage new router items API for cleaner, more maintainable code.
  - Simplified child factory usage in shared transition component for better context handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Patch:
diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api
index e59e21d1..689fe1ed 100644
--- a/decompose/api/android/decompose.api
+++ b/decompose/api/android/decompose.api
@@ -143,6 +143,16 @@ public abstract interface class com/arkivanov/decompose/router/children/NavState
 	public abstract fun getChildren ()Ljava/util/List;
 }
 
+public abstract interface class com/arkivanov/decompose/router/children/NavStateSaver {
+	public abstract fun restoreState (Lcom/arkivanov/essenty/statekeeper/SerializableContainer;)Ljava/lang/Object;
+	public abstract fun saveState (Ljava/lang/Object;)Lcom/arkivanov/essenty/statekeeper/SerializableContainer;
+}
+
+public final class com/arkivanov/decompose/router/children/NavStateSaverKt {
+	public static final fun NavStateSaver (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/arkivanov/decompose/router/children/NavStateSaver;
+	public static final fun NavStateSaver (Lkotlinx/serialization/KSerializer;)Lcom/arkivanov/decompose/router/children/NavStateSaver;
+}
+
 public abstract interface class com/arkivanov/decompose/router/children/NavigationSource {
 	public abstract fun subscribe (Lkotlin/jvm/functions/Function1;)Lcom/arkivanov/decompose/Cancellation;
 }
@@ -166,6 +176,106 @@ public final class com/arkivanov/decompose/router/children/SimpleNavigation : co
 	public fun subscribe (Lkotlin/jvm/functions/Function1;)Lcom/arkivanov/decompose/Cancellation;
 }
 
+public final class com/arkivanov/decompose/router/children/TransientNavStateSaverKt {
+	public static final fun transientNavStateSaver ()Lcom/arkivanov/decompose/router/children/NavStateSaver;
+}
+
+public final class com/arkivanov/decompose/router/items/ChildItems {
+	public fun <init> ()V
+	public fun <init> (Ljava/util/List;Ljava/util/Map;)V
+	public synthetic fun <init> (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final fun component1 ()Ljava/util/List;
+	public final fun component2 ()Ljava/util/Map;
+	public final fun copy (Ljava/util/List;Ljava/util/Map;)Lcom/arkivanov/decompose/router/items/ChildItems;
+	public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/items/ChildItems;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/items/ChildItems;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getActiveItems ()Ljava/util/Map;
+	public final fun getItems ()Ljava/util/List;
+	public fun hashCode ()I
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/arkivanov/decompose/router/items/ChildItemsFactoryKt {
+	public static final fun childItems (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lcom/arkivanov/decompose/router/children/NavStateSaver;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/router/items/LazyChildItems;
+	public static final fun childItems (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/router/items/LazyChildItems;
+	public static synthetic fun childItems$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lcom/arkivanov/decompose/router/children/NavStateSaver;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/items/LazyChildItems;
+	public static synthetic fun childItems$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/items/LazyChildItems;
+}
+
+public final class com/arkivanov/decompose/router/items/Items {
+	public static final field Companion Lcom/arkivanov/decompose/router/items/Items$Companion;
+	public fun <init> ()V
+	public fun <init> (Ljava/util/List;Ljava/util/Map;)V
+	public synthetic fun <init> (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final fun component1 ()Ljava/util/List;
+	public final fun component2 ()Ljava/util/Map;
+	public final fun copy (Ljava/util/List;Ljava/util/Map;)Lcom/arkivanov/decompose/router/items/Items;
+	public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/items/Items;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/items/Items;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getActiveItems ()Ljava/util/Map;
+	public final fun getItems ()Ljava/util/List;
+	public fun hashCode ()I
+	public fun toString ()Ljava/lang/String;
+}
+
+public synthetic class com/arkivanov/decompose/router/items/Items$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+	public fun <init> (Lkotlinx/serialization/KSerializer;)V
+	public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+	public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/arkivanov/decompose/router/items/Items;
+	public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+	public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+	public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/arkivanov/decompose/router/items/Items;)V
+	public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+	public final fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class com/arkivanov/decompose/router/items/Items$ActiveLifecycleState : java/lang/Enum {
+	public static final field CREATED Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+	public static final field RESUMED Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+	public static final field STARTED Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+	public static fun getEntries ()Lkotlin/enums/EnumEntries;
+	public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+	public static fun values ()[Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+}
+
+public final class com/arkivanov/decompose/router/items/Items$Companion {
+	public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
+}
+
+public abstract interface class com/arkivanov/decompose/router/items/ItemsNavigation : com/arkivanov/decompose/router/children/NavigationSource, com/arkivanov/decompose/router/items/ItemsNavigator {
+}
+
+public final class com/arkivanov/decompose/router/items/ItemsNavigation$Event {
+	public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+	public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final fun getOnComplete ()Lkotlin/jvm/functions/Function2;
+	public final fun getTransformer ()Lkotlin/jvm/functions/Function1;
+}
+
+public final class com/arkivanov/decompose/router/items/ItemsNavigationKt {
+	public static final fun ItemsNavigation ()Lcom/arkivanov/decompose/router/items/ItemsNavigation;
+}
+
+public abstract interface class com/arkivanov/decompose/router/items/ItemsNavigator {
+	public abstract fun navigate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+}
+
+public final class com/arkivanov/decompose/router/items/ItemsNavigatorExtKt {
+	public static final fun navigate (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;)V
+	public static final fun setActiveItems (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;)V
+	public static final fun setActiveItems (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+	public static synthetic fun setActiveItems$default (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+	public static final fun setItems (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;)V
+	public static final fun setItems (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+	public static synthetic fun setItems$default (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+}
+
+public abstract class com/arkivanov/decompose/router/items/LazyChildItems : com/arkivanov/decompose/value/Value, com/arkivanov/decompose/router/items/ItemsNavigator {
+	public fun <init> ()V
+	public abstract fun get (Ljava/lang/Object;)Ljava/lang/Object;
+	public abstract fun navigate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+}
+
 public final class com/arkivanov/decompose/router/pages/ChildPages {
 	public fun <init> ()V
 	public fun <init> (Ljava/util/List;I)V
diff --git a/decompose/api/decompose.klib.api b/decompose/api/decompose.klib.api
index 054c29ad..8c1263d4 100644
--- a/decompose/api/decompose.klib.api
+++ b/decompose/api/decompose.klib.api
@@ -69,6 +69,26 @@ abstract interface <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> com.arkivano
     abstract fun navigate(kotlin/Function1<com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>>, kotlin/Function2<com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit>) // com.arkivanov.decompose.router.panels/PanelsNavigator.navigate|navigate(kotlin.Function1<com.arkivanov.decompose.router.panels.Panels<1:0,1:1,1:2>,com.arkivanov.decompose.router.panels.Panels<1:0,1:1,1:2>>;kotlin.Function2<com.arkivanov.decompose.router.panels.Panels<1:0,1:1,1:2>,com.arkivanov.decompose.router.panels.Panels<1:0,1:1,1:2>,kotlin.Unit>){}[0]
 }
 
+abstract interface <#A: kotlin/Any> com.arkivanov.decompose.router.children/NavStateSaver { // com.arkivanov.decompose.router.children/NavStateSaver|null[0]
+    abstract fun restoreState(com.arkivanov.essenty.statekeeper/SerializableContainer): #A? // com.arkivanov.decompose.router.children/NavStateSaver.restoreState|restoreState(com.arkivanov.essenty.statekeeper.SerializableContainer){}[0]
+    abstract fun saveState(#A): com.arkivanov.essenty.statekeeper/SerializableContainer? // com.arkivanov.decompose.router.children/NavStateSaver.saveState|saveState(1:0){}[0]
+}
+
+abstract interface <#A: kotlin/Any> com.arkivanov.decompose.router.items/ItemsNavigation : com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.items/ItemsNavigation.Event<#A>>, com.arkivanov.decompose.router.items/ItemsNavigator<#A> { // com.arkivanov.decompose.router.items/ItemsNavigation|null[0]
+    final class <#A1: kotlin/Any> Event { // com.arkivanov.decompose.router.items/ItemsNavigation.Event|null[0]
+        constructor <init>(kotlin/Function1<com.arkivanov.decompose.router.items/Items<#A1>, com.arkivanov.decompose.router.items/Items<#A1>>, kotlin/Function2<com.arkivanov.decompose.router.items/Items<#A1>, com.arkivanov.decompose.router.items/Items<#A1>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.items/ItemsNavigation.Event.<init>|<init>(kotlin.Function1<com.arkivanov.decompose.router.items.Items<1:0>,com.arkivanov.decompose.router.items.Items<1:0>>;kotlin.Function2<com.arkivanov.decompose.router.items.Items<1:0>,com.arkivanov.decompose.router.items.Items<1:0>,kotlin.Unit>){}[0]
+
+        final val onComplete // com.arkivanov.decompose.router.items/ItemsNavigation.Event.onComplete|{}onComplete[0]
+            final fun <get-onComplete>(): kotlin/Function2<com.arkivanov.decompose.router.items/Items<#A1>, com.arkivanov.decompose.router.items/Items<#A1>, kotlin/Unit> // com.arkivanov.decompose.router.items/ItemsNavigation.Event.onComplete.<get-onComplete>|<get-onComplete>(){}[0]
+        final val transformer // com.arkivanov.decompose.router.items/ItemsNavigation.Event.transformer|{}transformer[0]
+            final fun <get-transformer>(): kotlin/Function1<com.arkivanov.decompose.router.items/Items<#A1>, com.arkivanov.decompose.router.items/Items<#A1>> // com.arkivanov.decompose.router.items/ItemsNavigation.Event.transformer.<get-transformer>|<get-transformer>(){}[0]
+    }
+}
+
+abstract interface <#A: kotlin/Any> com.arkivanov.decompose.router.items/ItemsNavigator { // com.arkivanov.decompose.router.items/ItemsNavigator|null[0]
+    abstract fun navigate(kotlin/Function1<com.arkivanov.decompose.router.items/Items<#A>, com.arkivanov.decompose.router.items/Items<#A>>, kotlin/Function2<com.arkivanov.decompose.router.items/Items<#A>, com.arkivanov.decompose.router.items/Items<#A>, kotlin/Unit>) // com.arkivanov.decompose.router.items/ItemsNavigator.navigate|navigate(kotlin.Function1<com.arkivanov.decompose.router.items.Items<1:0>,com.arkivanov.decompose.router.items.Items<1:0>>;kotlin.Function2<com.arkivanov.decompose.router.items.Items<1:0>,com.arkivanov.decompose.router.items.Items<1:0>,kotlin.Unit>){}[0]
+}
+
 abstract interface <#A: kotlin/Any> com.arkivanov.decompose.router.pages/PagesNavigation : com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.pages/PagesNavigation.Event<#A>>, com.arkivanov.decompose.router.pages/PagesNavigator<#A> { // com.arkivanov.decompose.router.pages/PagesNavigation|null[0]
     final class <#A1: kotlin/Any> Event { // com.arkivanov.decompose.router.pages/PagesNavigation.Event|null[0]
         constructor <init>(kotlin/Function1<com.arkivanov.decompose.router.pages/Pages<#A1>, com.arkivanov.decompose.router.pages/Pages<#A1>>, kotlin/Function2<com.arkivanov.decompose.router.pages/Pages<#A1>, com.arkivanov.decompose.router.pages/Pages<#A1>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.pages/PagesNavigation.Event.<init>|<init>(kotlin.Function1<com.arkivanov.decompose.router.pages.Pages<1:0>,com.arkivanov.decompose.router.pages.Pages<1:0>>;kotlin.Function2<com.arkivanov.decompose.router.pages.Pages<1:0>,com.arkivanov.decompose.router.pages.Pages<1:0>,kotlin.Unit>){}[0]
@@ -192,6 +212,13 @@ abstract interface com.arkivanov.decompose.router.webhistory/WebNavigationOwner
 
 abstract interface com.arkivanov.decompose/ComponentContext : com.arkivanov.decompose/GenericComponentContext<com.arkivanov.decompose/ComponentContext> // com.arkivanov.decompose/ComponentContext|null[0]
 
+abstract class <#A: kotlin/Any, #B: out kotlin/Any> com.arkivanov.decompose.router.items/LazyChildItems : com.arkivanov.decompose.router.items/ItemsNavigator<#A>, com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.items/ChildItems<#A, #B>> { // com.arkivanov.decompose.router.items/LazyChildItems|null[0]
+    constructor <init>() // com.arkivanov.decompose.router.items/LazyChildItems.<init>|<init>(){}[0]
+
+    abstract fun get(#A): #B // com.arkivanov.decompose.router.items/LazyChildItems.get|get(1:0){}[0]
+    abstract fun navigate(kotlin/Function1<com.arkivanov.decompose.router.items/Items<#A>, com.arkivanov.decompose.router.items/Items<#A>>, kotlin/Function2<com.arkivanov.decompose.router.items/Items<#A>, com.arkivanov.decompose.router.items/Items<#A>, kotlin/Unit>) // com.arkivanov.decompose.router.items/LazyChildItems.navigate|navigate(kotlin.Function1<com.arkivanov.decompose.router.items.Items<1:0>,com.arkivanov.decompose.router.items.Items<1:0>>;kotlin.Function2<com.arkivanov.decompose.router.items.Items<1:0>,com.arkivanov.decompose.router.items.Items<1:0>,kotlin.Unit>){}[0]
+}
+
 abstract class <#A: kotlin/Any> com.arkivanov.decompose.value/MutableValue : com.arkivanov.decompose.value/Value<#A> { // com.arkivanov.decompose.value/MutableValue|null[0]
     constructor <init>() // com.arkivanov.decompose.value/MutableValue.<init>|<init>(){}[0]
 
@@ -211,6 +238,22 @@ abstract class <#A: out kotlin/Any> com.arkivanov.decompose.value/Value { // com
     abstract fun subscribe(kotlin/Function1<#A, kotlin/Unit>): com.arkivanov.decompose/Cancellation // com.arkivanov.decompose.value/Value.subscribe|subscribe(kotlin.Function1<1:0,kotlin.Unit>){}[0]
 }
 
+final class <#A: kotlin/Any, #B: out kotlin/Any> com.arkivanov.decompose.router.items/ChildItems { // com.arkivanov.decompose.router.items/ChildItems|null[0]
+    constructor <init>(kotlin.collections/List<#A> = ..., kotlin.collections/Map<#A, kotlin/Pair<#B, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>> = ...) // com.arkivanov.decompose.router.items/ChildItems.<init>|<init>(kotlin.collections.List<1:0>;kotlin.collections.Map<1:0,kotlin.Pair<1:1,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>>){}[0]
+
+    final val activeItems // com.arkivanov.decompose.router.items/ChildItems.activeItems|{}activeItems[0]
+        final fun <get-activeItems>(): kotlin.collections/Map<#A, kotlin/Pair<#B, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>> // com.arkivanov.decompose.router.items/ChildItems.activeItems.<get-activeItems>|<get-activeItems>(){}[0]
+    final val items // com.arkivanov.decompose.router.items/ChildItems.items|{}items[0]
+        final fun <get-items>(): kotlin.collections/List<#A> // com.arkivanov.decompose.router.items/ChildItems.items.<get-items>|<get-items>(){}[0]
+
+    final fun component1(): kotlin.collections/List<#A> // com.arkivanov.decompose.router.items/ChildItems.component1|component1(){}[0]
+    final fun component2(): kotlin.collections/Map<#A, kotlin/Pair<#B, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>> // com.arkivanov.decompose.router.items/ChildItems.component2|component2(){}[0]
+    final fun copy(kotlin.collections/List<#A> = ..., kotlin.collections/Map<#A, kotlin/Pair<#B, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>> = ...): com.arkivanov.decompose.router.items/ChildItems<#A, #B> // com.arkivanov.decompose.router.items/ChildItems.copy|copy(kotlin.collections.List<1:0>;kotlin.collections.Map<1:0,kotlin.Pair<1:1,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>>){}[0]
+    final fun equals(kotlin/Any?): kotlin/Boolean // com.arkivanov.decompose.router.items/ChildItems.equals|equals(kotlin.Any?){}[0]
+    final fun hashCode(): kotlin/Int // com.arkivanov.decompose.router.items/ChildItems.hashCode|hashCode(){}[0]
+    final fun toString(): kotlin/String // com.arkivanov.decompose.router.items/ChildItems.toString|toString(){}[0]
+}
+
 final class <#A: kotlin/Any> com.arkivanov.decompose.router.children/SimpleNavigation : com.arkivanov.decompose.router.children/NavigationSource<#A> { // com.arkivanov.decompose.router.children/SimpleNavigation|null[0]
     constructor <init>() // com.arkivanov.decompose.router.children/SimpleNavigation.<init>|<init>(){}[0]
 
@@ -218,6 +261,55 @@ final class <#A: kotlin/Any> com.arkivanov.decompose.router.children/SimpleNavig
     final fun subscribe(kotlin/Function1<#A, kotlin/Unit>): com.arkivanov.decompose/Cancellation // com.arkivanov.decompose.router.children/SimpleNavigation.subscribe|subscribe(kotlin.Function1<1:0,kotlin.Unit>){}[0]
 }
 
+final class <#A: kotlin/Any> com.arkivanov.decompose.router.items/Items { // com.arkivanov.decompose.router.items/Items|null[0]
+    constructor <init>(kotlin.collections/List<#A> = ..., kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState> = ...) // com.arkivanov.decompose.router.items/Items.<init>|<init>(kotlin.collections.List<1:0>;kotlin.collections.Map<1:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>){}[0]
+
+    final val activeItems // com.arkivanov.decompose.router.items/Items.activeItems|{}activeItems[0]
+        final fun <get-activeItems>(): kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState> // com.arkivanov.decompose.router.items/Items.activeItems.<get-activeItems>|<get-activeItems>(){}[0]
+    final val items // com.arkivanov.decompose.router.items/Items.items|{}items[0]
+        final fun <get-items>(): kotlin.collections/List<#A> // com.arkivanov.decompose.router.items/Items.items.<get-items>|<get-items>(){}[0]
+
+    final fun component1(): kotlin.collections/List<#A> // com.arkivanov.decompose.router.items/Items.component1|component1(){}[0]
+    final fun component2(): kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState> // com.arkivanov.decompose.router.items/Items.component2|component2(){}[0]
+    final fun copy(kotlin.collections/List<#A> = ..., kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState> = ...): com.arkivanov.decompose.router.items/Items<#A> // com.arkivanov.decompose.router.items/Items.copy|copy(kotlin.collections.List<1:0>;kotlin.collections.Map<1:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>){}[0]
+    final fun equals(kotlin/Any?): kotlin/Boolean // com.arkivanov.decompose.router.items/Items.equals|equals(kotlin.Any?){}[0]
+    final fun hashCode(): kotlin/Int // com.arkivanov.decompose.router.items/Items.hashCode|hashCode(){}[0]
+    final fun toString(): kotlin/String // com.arkivanov.decompose.router.items/Items.toString|toString(){}[0]
+
+    final enum class ActiveLifecycleState : kotlin/Enum<com.arkivanov.decompose.router.items/Items.ActiveLifecycleState> { // com.arkivanov.decompose.router.items/Items.ActiveLifecycleState|null[0]
+        enum entry CREATED // com.arkivanov.decompose.router.items/Items.ActiveLifecycleState.CREATED|null[0]
+        enum entry RESUMED // com.arkivanov.decompose.router.items/Items.ActiveLifecycleState.RESUMED|null[0]
+        enum entry STARTED // com.arkivanov.decompose.router.items/Items.ActiveLifecycleState.STARTED|null[0]
+
+        final val entries // com.arkivanov.decompose.router.items/Items.ActiveLifecycleState.entries|#static{}entries[0]
+            final fun <get-entries>(): kotlin.enums/EnumEntries<com.arkivanov.decompose.router.items/Items.ActiveLifecycleState> // com.arkivanov.decompose.router.items/Items.ActiveLifecycleState.entries.<get-entries>|<get-entries>#static(){}[0]
+
+        final fun valueOf(kotlin/String): com.arkivanov.decompose.router.items/Items.ActiveLifecycleState // com.arkivanov.decompose.router.items/Items.ActiveLifecycleState.valueOf|valueOf#static(kotlin.String){}[0]
+        final fun values(): kotlin/Array<com.arkivanov.decompose.router.items/Items.ActiveLifecycleState> // com.arkivanov.decompose.router.items/Items.ActiveLifecycleState.values|values#static(){}[0]
+    }
+
+    final class <#A1: kotlin/Any?> $serializer : kotlinx.serialization.internal/GeneratedSerializer<com.arkivanov.decompose.router.items/Items<#A1>> { // com.arkivanov.decompose.router.items/Items.$serializer|null[0]
+        constructor <init>(kotlinx.serialization/KSerializer<#A1>) // com.arkivanov.decompose.router.items/Items.$serializer.<init>|<init>(kotlinx.serialization.KSerializer<1:0>){}[0]
+
+        final val descriptor // com.arkivanov.decompose.router.items/Items.$serializer.descriptor|{}descriptor[0]
+            final fun <get-descriptor>(): kotlinx.serialization.descriptors/SerialDescriptor // com.arkivanov.decompose.router.items/Items.$serializer.descriptor.<get-descriptor>|<get-descriptor>(){}[0]
+        final val typeSerial0 // com.arkivanov.decompose.router.items/Items.$serializer.typeSerial0|{}typeSerial0[0]
+
+        final fun childSerializers(): kotlin/Array<kotlinx.serialization/KSerializer<*>> // com.arkivanov.decompose.router.items/Items.$serializer.childSerializers|childSerializers(){}[0]
+        final fun deserialize(kotlinx.serialization.encoding/Decoder): com.arkivanov.decompose.router.items/Items<#A1> // com.arkivanov.decompose.router.items/Items.$serializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0]
+        final fun serialize(kotlinx.serialization.encoding/Encoder, com.arkivanov.decompose.router.items/Items<#A1>) // com.arkivanov.decompose.router.items/Items.$serializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;com.arkivanov.decompose.router.items.Items<1:0>){}[0]
+        final fun typeParametersSerializers(): kotlin/Array<kotlinx.serialization/KSerializer<*>> // com.arkivanov.decompose.router.items/Items.$serializer.typeParametersSerializers|typeParametersSerializers(){}[0]
+    }
+
+    final object Companion : kotlinx.serialization.internal/SerializerFactory { // com.arkivanov.decompose.router.items/Items.Companion|null[0]
+        final val $cachedDescriptor // com.arkivanov.decompose.router.items/Items.Companion.$cachedDescriptor|{}$cachedDescriptor[0]
+            final fun <get-$cachedDescriptor>(): kotlinx.serialization.descriptors/SerialDescriptor // com.arkivanov.decompose.router.items/Items.Companion.$cachedDescriptor.<get-$cachedDescriptor>|<get-$cachedDescriptor>(){}[0]
+
+        final fun <#A2: kotlin/Any?> serializer(kotlinx.serialization/KSerializer<#A2>): kotlinx.serialization/KSerializer<com.arkivanov.decompose.router.items/Items<#A2>> // com.arkivanov.decompose.router.items/Items.Companion.serializer|serializer(kotlinx.serialization.KSerializer<0:0>){0§<kotlin.Any?>}[0]
+        final fun serializer(kotlin/Array<out kotlinx.serialization/KSerializer<*>>...): kotlinx.serialization/KSerializer<*> // com.arkivanov.decompose.router.items/Items.Companion.serializer|serializer(kotlin.Array<out|kotlinx.serialization.KSerializer<*>>...){}[0]
+    }
+}
+
 final class <#A: out kotlin/Any, #B: out kotlin/Any, #C: out kotlin/Any, #D: out kotlin/Any, #E: out kotlin/Any, #F: out kotlin/Any> com.arkivanov.decompose.router.panels/ChildPanels { // com.arkivanov.decompose.router.panels/ChildPanels|null[0]
     constructor <init>(com.arkivanov.decompose/Child.Created<#A, #B>, com.arkivanov.decompose/Child.Created<#C, #D>? = ..., com.arkivanov.decompose/Child.Created<#E, #F>? = ..., com.arkivanov.decompose.router.panels/ChildPanelsMode = ...) // com.arkivanov.decompose.router.panels/ChildPanels.<init>|<init>(com.arkivanov.decompose.Child.Created<1:0,1:1>;com.arkivanov.decompose.Child.Created<1:2,1:3>?;com.arkivanov.decompose.Child.Created<1:4,1:5>?;com.arkivanov.decompose.router.panels.ChildPanelsMode){}[0]
 
@@ -487,6 +579,8 @@ final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/A
 final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any, #F: kotlin/Any, #G: kotlin/Any> (#A).com.arkivanov.decompose.router.panels/childPanels(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.panels/PanelsNavigation.Event<#B, #D, #F>>, kotlin/Function0<com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>>, kotlin/Function1<com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>, com.arkivanov.essenty.statekeeper/SerializableContainer?>, kotlin/Function1<com.arkivanov.essenty.statekeeper/SerializableContainer, com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>?>, kotlin/String = ..., kotlin/Function2<com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>, com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>?, kotlin/Unit> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>, kotlin/Function2<#D, #A, #E>, kotlin/Function2<#F, #A, #G>): com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.panels/ChildPanels<#B, #C, #D, #E, #F, #G>> // com.arkivanov.decompose.router.panels/childPanels|childPanels@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.panels.PanelsNavigation.Event<0:1,0:3,0:5>>;kotlin.Function0<com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>>;kotlin.Function1<com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>,com.arkivanov.essenty.statekeeper.SerializableContainer?>;kotlin.Function1<com.arkivanov.essenty.statekeeper.SerializableContainer,com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>?>;kotlin.String;kotlin.Function2<com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>,com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>?,kotlin.Unit>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>;kotlin.Function2<0:3,0:0,0:4>;kotlin.Function2<0:5,0:0,0:6>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>;3§<kotlin.Any>;4§<kotlin.Any>;5§<kotlin.Any>;6§<kotlin.Any>}[0]
 final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any, #F: kotlin/Any, #G: kotlin/Any> (#A).com.arkivanov.decompose.router.panels/childPanels(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.panels/PanelsNavigation.Event<#B, #D, #F>>, kotlin/Triple<kotlinx.serialization/KSerializer<#B>, kotlinx.serialization/KSerializer<#D>, kotlinx.serialization/KSerializer<#F>>?, kotlin/Function0<com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>>, kotlin/String = ..., kotlin/Function2<com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>, com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>?, kotlin/Unit> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>, kotlin/Function2<#D, #A, #E>, kotlin/Function2<#F, #A, #G>): com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.panels/ChildPanels<#B, #C, #D, #E, #F, #G>> // com.arkivanov.decompose.router.panels/childPanels|childPanels@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.panels.PanelsNavigation.Event<0:1,0:3,0:5>>;kotlin.Triple<kotlinx.serialization.KSerializer<0:1>,kotlinx.serialization.KSerializer<0:3>,kotlinx.serialization.KSerializer<0:5>>?;kotlin.Function0<com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>>;kotlin.String;kotlin.Function2<com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>,com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>?,kotlin.Unit>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>;kotlin.Function2<0:3,0:0,0:4>;kotlin.Function2<0:5,0:0,0:6>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>;3§<kotlin.Any>;4§<kotlin.Any>;5§<kotlin.Any>;6§<kotlin.Any>}[0]
 final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any> (#A).com.arkivanov.decompose.router.panels/childPanels(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.panels/PanelsNavigation.Event<#B, #D, kotlin/Nothing>>, kotlin/Pair<kotlinx.serialization/KSerializer<#B>, kotlinx.serialization/KSerializer<#D>>?, kotlin/Function0<com.arkivanov.decompose.router.panels/Panels<#B, #D, kotlin/Nothing>>, kotlin/String = ..., kotlin/Function2<com.arkivanov.decompose.router.panels/Panels<#B, #D, kotlin/Nothing>, com.arkivanov.decompose.router.panels/Panels<#B, #D, kotlin/Nothing>?, kotlin/Unit> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>, kotlin/Function2<#D, #A, #E>): com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.panels/ChildPanels<#B, #C, #D, #E, kotlin/Nothing, kotlin/Nothing>> // com.arkivanov.decompose.router.panels/childPanels|childPanels@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.panels.PanelsNavigation.Event<0:1,0:3,kotlin.Nothing>>;kotlin.Pair<kotlinx.serialization.KSerializer<0:1>,kotlinx.serialization.KSerializer<0:3>>?;kotlin.Function0<com.arkivanov.decompose.router.panels.Panels<0:1,0:3,kotlin.Nothing>>;kotlin.String;kotlin.Function2<com.arkivanov.decompose.router.panels.Panels<0:1,0:3,kotlin.Nothing>,com.arkivanov.decompose.router.panels.Panels<0:1,0:3,kotlin.Nothing>?,kotlin.Unit>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>;kotlin.Function2<0:3,0:0,0:4>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>;3§<kotlin.Any>;4§<kotlin.Any>}[0]
+final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.items/childItems(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.items/ItemsNavigation.Event<#B>>, com.arkivanov.decompose.router.children/NavStateSaver<com.arkivanov.decompose.router.items/Items<#B>>?, kotlin/Function0<com.arkivanov.decompose.router.items/Items<#B>>, kotlin/String = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.router.items/LazyChildItems<#B, #C> // com.arkivanov.decompose.router.items/childItems|childItems@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.items.ItemsNavigation.Event<0:1>>;com.arkivanov.decompose.router.children.NavStateSaver<com.arkivanov.decompose.router.items.Items<0:1>>?;kotlin.Function0<com.arkivanov.decompose.router.items.Items<0:1>>;kotlin.String;kotlin.Function2<0:1,0:0,0:2>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>}[0]
+final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.items/childItems(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.items/ItemsNavigation.Event<#B>>, kotlinx.serialization/KSerializer<#B>?, kotlin/Function0<com.arkivanov.decompose.router.items/Items<#B>>, kotlin/String = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.router.items/LazyChildItems<#B, #C> // com.arkivanov.decompose.router.items/childItems|childItems@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.items.ItemsNavigation.Event<0:1>>;kotlinx.serialization.KSerializer<0:1>?;kotlin.Function0<com.arkivanov.decompose.router.items.Items<0:1>>;kotlin.String;kotlin.Function2<0:1,0:0,0:2>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>}[0]
 final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.pages/childPages(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.pages/PagesNavigation.Event<#B>>, kotlin/Function0<com.arkivanov.decompose.router.pages/Pages<#B>>, kotlin/Function1<com.arkivanov.decompose.router.pages/Pages<#B>, com.arkivanov.essenty.statekeeper/SerializableContainer?>, kotlin/Function1<com.arkivanov.essenty.statekeeper/SerializableContainer, com.arkivanov.decompose.router.pages/Pages<#B>?>, kotlin/String = ..., kotlin/Function2<kotlin/Int, com.arkivanov.decompose.router.pages/Pages<#B>, com.arkivanov.decompose.router.children/ChildNavState.Status> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.pages/ChildPages<#B, #C>> // com.arkivanov.decompose.router.pages/childPages|childPages@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.pages.PagesNavigation.Event<0:1>>;kotlin.Function0<com.arkivanov.decompose.router.pages.Pages<0:1>>;kotlin.Function1<com.arkivanov.decompose.router.pages.Pages<0:1>,com.arkivanov.essenty.statekeeper.SerializableContainer?>;kotlin.Function1<com.arkivanov.essenty.statekeeper.SerializableContainer,com.arkivanov.decompose.router.pages.Pages<0:1>?>;kotlin.String;kotlin.Function2<kotlin.Int,com.arkivanov.decompose.router.pages.Pages<0:1>,com.arkivanov.decompose.router.children.ChildNavState.Status>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>}[0]
 final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.pages/childPages(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.pages/PagesNavigation.Event<#B>>, kotlinx.serialization/KSerializer<#B>?, kotlin/Function0<com.arkivanov.decompose.router.pages/Pages<#B>> = ..., kotlin/String = ..., kotlin/Function2<kotlin/Int, com.arkivanov.decompose.router.pages/Pages<#B>, com.arkivanov.decompose.router.children/ChildNavState.Status> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.pages/ChildPages<#B, #C>> // com.arkivanov.decompose.router.pages/childPages|childPages@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.pages.PagesNavigation.Event<0:1>>;kotlinx.serialization.KSerializer<0:1>?;kotlin.Function0<com.arkivanov.decompose.router.pages.Pages<0:1>>;kotlin.String;kotlin.Function2<kotlin.Int,com.arkivanov.decompose.router.pages.Pages<0:1>,com.arkivanov.decompose.router.children.ChildNavState.Status>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>}[0]
 final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.slot/childSlot(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.slot/SlotNavigation.Event<#B>>, kotlin/Function1<#B?, com.arkivanov.essenty.statekeeper/SerializableContainer?>, kotlin/Function1<com.arkivanov.essenty.statekeeper/SerializableContainer, #B?>, kotlin/String = ..., kotlin/Function0<#B?> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.slot/ChildSlot<#B, #C>> // com.arkivanov.decompose.router.slot/childSlot|childSlot@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.slot.SlotNavigation.Event<0:1>>;kotlin.Function1<0:1?,com.arkivanov.essenty.statekeeper.SerializableContainer?>;kotlin.Function1<com.arkivanov.essenty.statekeeper.SerializableContainer,0:1?>;kotlin.String;kotlin.Function0<0:1?>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>}[0]
@@ -513,6 +607,8 @@ final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> com.arkivanov.decompo
 final fun <#A: kotlin/Any, #B: kotlin/Any> (com.arkivanov.decompose.value/Value<#A>).com.arkivanov.decompose.value.operator/map(kotlin/Function1<#A, #B>): com.arkivanov.decompose.value/Value<#B> // com.arkivanov.decompose.value.operator/map|map@com.arkivanov.decompose.value.Value<0:0>(kotlin.Function1<0:0,0:1>){0§<kotlin.Any>;1§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any, #B: kotlin/Any> com.arkivanov.decompose.router.pages/childPagesWebNavigation(com.arkivanov.decompose.router.pages/PagesNavigator<#A>, com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.pages/ChildPages<#A, #B>>, kotlinx.serialization/KSerializer<#A>, kotlin/Function1<com.arkivanov.decompose.router.pages/ChildPages<#A, #B>, kotlin/String?> = ..., kotlin/Function1<com.arkivanov.decompose.router.pages/ChildPages<#A, #B>, kotlin.collections/Map<kotlin/String, kotlin/String>?> = ..., kotlin/Function0<kotlin/Boolean> = ..., kotlin/Function1<com.arkivanov.decompose/Child.Created<#A, #B>, com.arkivanov.decompose.router.webhistory/WebNavigationOwner?> = ...): com.arkivanov.decompose.router.webhistory/WebNavigation<*> // com.arkivanov.decompose.router.pages/childPagesWebNavigation|childPagesWebNavigation(com.arkivanov.decompose.router.pages.PagesNavigator<0:0>;com.arkivanov.decompose.value.Value<com.arkivanov.decompose.router.pages.ChildPages<0:0,0:1>>;kotlinx.serialization.KSerializer<0:0>;kotlin.Function1<com.arkivanov.decompose.router.pages.ChildPages<0:0,0:1>,kotlin.String?>;kotlin.Function1<com.arkivanov.decompose.router.pages.ChildPages<0:0,0:1>,kotlin.collections.Map<kotlin.String,kotlin.String>?>;kotlin.Function0<kotlin.Boolean>;kotlin.Function1<com.arkivanov.decompose.Child.Created<0:0,0:1>,com.arkivanov.decompose.router.webhistory.WebNavigationOwner?>){0§<kotlin.Any>;1§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any, #B: kotlin/Any> com.arkivanov.decompose.router.stack/childStackWebNavigation(com.arkivanov.decompose.router.stack/StackNavigator<#A>, com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.stack/ChildStack<#A, #B>>, kotlinx.serialization/KSerializer<#A>, kotlin/Boolean = ..., kotlin/Function1<com.arkivanov.decompose/Child.Created<#A, #B>, kotlin/String?> = ..., kotlin/Function1<com.arkivanov.decompose/Child.Created<#A, #B>, kotlin.collections/Map<kotlin/String, kotlin/String>?> = ..., kotlin/Function0<kotlin/Boolean> = ..., kotlin/Function1<com.arkivanov.decompose/Child.Created<#A, #B>, com.arkivanov.decompose.router.webhistory/WebNavigationOwner?> = ...): com.arkivanov.decompose.router.webhistory/WebNavigation<*> // com.arkivanov.decompose.router.stack/childStackWebNavigation|childStackWebNavigation(com.arkivanov.decompose.router.stack.StackNavigator<0:0>;com.arkivanov.decompose.value.Value<com.arkivanov.decompose.router.stack.ChildStack<0:0,0:1>>;kotlinx.serialization.KSerializer<0:0>;kotlin.Boolean;kotlin.Function1<com.arkivanov.decompose.Child.Created<0:0,0:1>,kotlin.String?>;kotlin.Function1<com.arkivanov.decompose.Child.Created<0:0,0:1>,kotlin.collections.Map<kotlin.String,kotlin.String>?>;kotlin.Function0<kotlin.Boolean>;kotlin.Function1<com.arkivanov.decompose.Child.Created<0:0,0:1>,com.arkivanov.decompose.router.webhistory.WebNavigationOwner?>){0§<kotlin.Any>;1§<kotlin.Any>}[0]
+final fun <#A: kotlin/Any> (com.arkivanov.decompose.router.items/ItemsNavigator<#A>).com.arkivanov.decompose.router.items/navigate(kotlin/Function1<com.arkivanov.decompose.router.items/Items<#A>, com.arkivanov.decompose.router.items/Items<#A>>) // com.arkivanov.decompose.router.items/navigate|navigate@com.arkivanov.decompose.router.items.ItemsNavigator<0:0>(kotlin.Function1<com.arkivanov.decompose.router.items.Items<0:0>,com.arkivanov.decompose.router.items.Items<0:0>>){0§<kotlin.Any>}[0]
+final fun <#A: kotlin/Any> (com.arkivanov.decompose.router.items/ItemsNavigator<#A>).com.arkivanov.decompose.router.items/setItems(kotlin/Function1<kotlin.collections/List<#A>, kotlin.collections/List<#A>>) // com.arkivanov.decompose.router.items/setItems|setItems@com.arkivanov.decompose.router.items.ItemsNavigator<0:0>(kotlin.Function1<kotlin.collections.List<0:0>,kotlin.collections.List<0:0>>){0§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any> (com.arkivanov.decompose.router.pages/PagesNavigator<#A>).com.arkivanov.decompose.router.pages/clear(kotlin/Function2<com.arkivanov.decompose.router.pages/Pages<#A>, com.arkivanov.decompose.router.pages/Pages<#A>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.pages/clear|clear@com.arkivanov.decompose.router.pages.PagesNavigator<0:0>(kotlin.Function2<com.arkivanov.decompose.router.pages.Pages<0:0>,com.arkivanov.decompose.router.pages.Pages<0:0>,kotlin.Unit>){0§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any> (com.arkivanov.decompose.router.pages/PagesNavigator<#A>).com.arkivanov.decompose.router.pages/navigate(kotlin/Function1<com.arkivanov.decompose.router.pages/Pages<#A>, com.arkivanov.decompose.router.pages/Pages<#A>>) // com.arkivanov.decompose.router.pages/navigate|navigate@com.arkivanov.decompose.router.pages.PagesNavigator<0:0>(kotlin.Function1<com.arkivanov.decompose.router.pages.Pages<0:0>,com.arkivanov.decompose.router.pages.Pages<0:0>>){0§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any> (com.arkivanov.decompose.router.pages/PagesNavigator<#A>).com.arkivanov.decompose.router.pages/select(kotlin/Int, kotlin/Function2<com.arkivanov.decompose.router.pages/Pages<#A>, com.arkivanov.decompose.router.pages/Pages<#A>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.pages/select|select@com.arkivanov.decompose.router.pages.PagesNavigator<0:0>(kotlin.Int;kotlin.Function2<com.arkivanov.decompose.router.pages.Pages<0:0>,com.arkivanov.decompose.router.pages.Pages<0:0>,kotlin.Unit>){0§<kotlin.Any>}[0]
@@ -527,6 +623,9 @@ final fun <#A: kotlin/Any> (com.arkivanov.decompose.value/MutableValue<#A>).com.
 final fun <#A: kotlin/Any> (com.arkivanov.decompose.value/MutableValue<#A>).com.arkivanov.decompose.value/updateAndGet(kotlin/Function1<#A, #A>): #A // com.arkivanov.decompose.value/updateAndGet|updateAndGet@com.arkivanov.decompose.value.MutableValue<0:0>(kotlin.Function1<0:0,0:0>){0§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any> (com.arkivanov.decompose.value/Value<#A>).com.arkivanov.decompose.value/getValue(kotlin/Any?, kotlin.reflect/KProperty<*>): #A // com.arkivanov.decompose.value/getValue|getValue@com.arkivanov.decompose.value.Value<0:0>(kotlin.Any?;kotlin.reflect.KProperty<*>){0§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any> (com.arkivanov.decompose.value/Value<#A>).com.arkivanov.decompose.value/subscribe(com.arkivanov.essenty.lifecycle/Lifecycle, com.arkivanov.decompose.value/ObserveLifecycleMode = ..., kotlin/Function1<#A, kotlin/Unit>) // com.arkivanov.decompose.value/subscribe|subscribe@com.arkivanov.decompose.value.Value<0:0>(com.arkivanov.essenty.lifecycle.Lifecycle;com.arkivanov.decompose.value.ObserveLifecycleMode;kotlin.Function1<0:0,kotlin.Unit>){0§<kotlin.Any>}[0]
+final fun <#A: kotlin/Any> com.arkivanov.decompose.router.children/NavStateSaver(kotlinx.serialization/KSerializer<#A>): com.arkivanov.decompose.router.children/NavStateSaver<#A> // com.arkivanov.decompose.router.children/NavStateSaver|NavStateSaver(kotlinx.serialization.KSerializer<0:0>){0§<kotlin.Any>}[0]
+final fun <#A: kotlin/Any> com.arkivanov.decompose.router.children/transientNavStateSaver(): com.arkivanov.decompose.router.children/NavStateSaver<#A> // com.arkivanov.decompose.router.children/transientNavStateSaver|transientNavStateSaver(){0§<kotlin.Any>}[0]
+final fun <#A: kotlin/Any> com.arkivanov.decompose.router.items/ItemsNavigation(): com.arkivanov.decompose.router.items/ItemsNavigation<#A> // com.arkivanov.decompose.router.items/ItemsNavigation|ItemsNavigation(){0§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any> com.arkivanov.decompose.router.pages/PagesNavigation(): com.arkivanov.decompose.router.pages/PagesNavigation<#A> // com.arkivanov.decompose.router.pages/PagesNavigation|PagesNavigation(){0§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any> com.arkivanov.decompose.router.slot/SlotNavigation(): com.arkivanov.decompose.router.slot/SlotNavigation<#A> // com.arkivanov.decompose.router.slot/SlotNavigation|SlotNavigation(){0§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any> com.arkivanov.decompose.router.stack/StackNavigation(): com.arkivanov.decompose.router.stack/StackNavigation<#A> // com.arkivanov.decompose.router.stack/StackNavigation|StackNavigation(){0§<kotlin.Any>}[0]
@@ -534,6 +633,9 @@ final fun <#A: kotlin/Any> com.arkivanov.decompose.value/MutableValue(#A): com.a
 final fun com.arkivanov.decompose.router.pages/getDefaultPageStatus(kotlin/Int, com.arkivanov.decompose.router.pages/Pages<*>): com.arkivanov.decompose.router.children/ChildNavState.Status // com.arkivanov.decompose.router.pages/getDefaultPageStatus|getDefaultPageStatus(kotlin.Int;com.arkivanov.decompose.router.pages.Pages<*>){}[0]
 final inline fun (com.arkivanov.decompose.router.slot/SlotNavigator<*>).com.arkivanov.decompose.router.slot/dismiss(crossinline kotlin/Function1<kotlin/Boolean, kotlin/Unit> = ...) // com.arkivanov.decompose.router.slot/dismiss|dismiss@com.arkivanov.decompose.router.slot.SlotNavigator<*>(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
 final inline fun (com.arkivanov.decompose.router.stack/StackNavigator<*>).com.arkivanov.decompose.router.stack/popToFirst(crossinline kotlin/Function1<kotlin/Boolean, kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/popToFirst|popToFirst@com.arkivanov.decompose.router.stack.StackNavigator<*>(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
+final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.items/ItemsNavigator<#A>).com.arkivanov.decompose.router.items/setActiveItems(crossinline kotlin/Function1<kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>, kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>>) // com.arkivanov.decompose.router.items/setActiveItems|setActiveItems@com.arkivanov.decompose.router.items.ItemsNavigator<0:0>(kotlin.Function1<kotlin.collections.Map<0:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>,kotlin.collections.Map<0:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>>){0§<kotlin.Any>}[0]
+final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.items/ItemsNavigator<#A>).com.arkivanov.decompose.router.items/setActiveItems(crossinline kotlin/Function1<kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>, kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>>, crossinline kotlin/Function2<com.arkivanov.decompose.router.items/Items<#A>, com.arkivanov.decompose.router.items/Items<#A>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.items/setActiveItems|setActiveItems@com.arkivanov.decompose.router.items.ItemsNavigator<0:0>(kotlin.Function1<kotlin.collections.Map<0:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>,kotlin.collections.Map<0:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>>;kotlin.Function2<com.arkivanov.decompose.router.items.Items<0:0>,com.arkivanov.decompose.router.items.Items<0:0>,kotlin.Unit>){0§<kotlin.Any>}[0]
+final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.items/ItemsNavigator<#A>).com.arkivanov.decompose.router.items/setItems(crossinline kotlin/Function1<kotlin.collections/List<#A>, kotlin.collections/List<#A>>, crossinline kotlin/Function2<com.arkivanov.decompose.router.items/Items<#A>, com.arkivanov.decompose.router.items/Items<#A>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.items/setItems|setItems@com.arkivanov.decompose.router.items.ItemsNavigator<0:0>(kotlin.Function1<kotlin.collections.List<0:0>,kotlin.collections.List<0:0>>;kotlin.Function2<com.arkivanov.decompose.router.items.Items<0:0>,com.arkivanov.decompose.router.items.Items<0:0>,kotlin.Unit>){0§<kotlin.Any>}[0]
 final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.slot/SlotNavigator<#A>).com.arkivanov.decompose.router.slot/activate(#A, crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.slot/activate|activate@com.arkivanov.decompose.router.slot.SlotNavigator<0:0>(0:0;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
 final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/bringToFront(#A, crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/bringToFront|bringToFront@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(0:0;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
 final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/pop(crossinline kotlin/Function1<kotlin/Boolean, kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/pop|pop@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){0§<kotlin.Any>}[0]
@@ -545,6 +647,7 @@ final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNav
 final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/pushToFront(#A, crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/pushToFront|pushToFront@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(0:0;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
 final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/replaceAll(kotlin/Array<out #A>..., crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/replaceAll|replaceAll@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(kotlin.Array<out|0:0>...;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
 final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/replaceCurrent(#A, crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/replaceCurrent|replaceCurrent@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(0:0;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
+final inline fun <#A: kotlin/Any> com.arkivanov.decompose.router.children/NavStateSaver(crossinline kotlin/Function1<#A, com.arkivanov.essenty.statekeeper/SerializableContainer?>, crossinline kotlin/Function1<com.arkivanov.essenty.statekeeper/SerializableContainer, #A?>): com.arkivanov.decompose.router.children/NavStateSaver<#A> // com.arkivanov.decompose.router.children/NavStateSaver|NavStateSaver(kotlin.Function1<0:0,com.arkivanov.essenty.statekeeper.SerializableContainer?>;kotlin.Function1<com.arkivanov.essenty.statekeeper.SerializableContainer,0:0?>){0§<kotlin.Any>}[0]
 
 // Targets: [js, wasmJs]
 final class com.arkivanov.decompose.router.stack.webhistory/DefaultWebHistoryController : com.arkivanov.decompose.router.stack.webhistory/WebHistoryController { // com.arkivanov.decompose.router.stack.webhistory/DefaultWebHistoryController|null[0]
diff --git a/decompose/api/jvm/decompose.api b/decompose/api/jvm/decompose.api
index 4e9a33fc..ee4eaf17 100644
--- a/decompose/api/jvm/decompose.api
+++ b/decompose/api/jvm/decompose.api
@@ -123,6 +123,16 @@ public abstract interface class com/arkivanov/decompose/router/children/NavState
 	public abstract fun getChildren ()Ljava/util/List;
 }
 
+public abstract interface class com/arkivanov/decompose/router/children/NavStateSaver {
+	public abstract fun restoreState (Lcom/arkivanov/essenty/statekeeper/SerializableContainer;)Ljava/lang/Object;
+	public abstract fun saveState (Ljava/lang/Object;)Lcom/arkivanov/essenty/statekeeper/SerializableContainer;
+}
+
+public final class com/arkivanov/decompose/router/children/NavStateSaverKt {
+	public static final fun NavStateSaver (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/arkivanov/decompose/router/children/NavStateSaver;
+	public static final fun NavStateSaver (Lkotlinx/serialization/KSerializer;)Lcom/arkivanov/decompose/router/children/NavStateSaver;
+}
+
 public abstract interface class com/arkivanov/decompose/router/children/NavigationSource {
 	public abstract fun subscribe (Lkotlin/jvm/functions/Function1;)Lcom/arkivanov/decompose/Cancellation;
 }
@@ -146,6 +156,106 @@ public final class com/arkivanov/decompose/router/children/SimpleNavigation : co
 	public fun subscribe (Lkotlin/jvm/functions/Function1;)Lcom/arkivanov/decompose/Cancellation;
 }
 
+public final class com/arkivanov/decompose/router/children/TransientNavStateSaverKt {
+	public static final fun transientNavStateSaver ()Lcom/arkivanov/decompose/router/children/NavStateSaver;
+}
+
+public final class com/arkivanov/decompose/router/items/ChildItems {
+	public fun <init> ()V
+	public fun <init> (Ljava/util/List;Ljava/util/Map;)V
+	public synthetic fun <init> (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final fun component1 ()Ljava/util/List;
+	public final fun component2 ()Ljava/util/Map;
+	public final fun copy (Ljava/util/List;Ljava/util/Map;)Lcom/arkivanov/decompose/router/items/ChildItems;
+	public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/items/ChildItems;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/items/ChildItems;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getActiveItems ()Ljava/util/Map;
+	public final fun getItems ()Ljava/util/List;
+	public fun hashCode ()I
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/arkivanov/decompose/router/items/ChildItemsFactoryKt {
+	public static final fun childItems (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lcom/arkivanov/decompose/router/children/NavStateSaver;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/router/items/LazyChildItems;
+	public static final fun childItems (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/router/items/LazyChildItems;
+	public static synthetic fun childItems$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lcom/arkivanov/decompose/router/children/NavStateSaver;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/items/LazyChildItems;
+	public static synthetic fun childItems$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/items/LazyChildItems;
+}
+
+public final class com/arkivanov/decompose/router/items/Items {
+	public static final field Companion Lcom/arkivanov/decompose/router/items/Items$Companion;
+	public fun <init> ()V
+	public fun <init> (Ljava/util/List;Ljava/util/Map;)V
+	public synthetic fun <init> (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final fun component1 ()Ljava/util/List;
+	public final fun component2 ()Ljava/util/Map;
+	public final fun copy (Ljava/util/List;Ljava/util/Map;)Lcom/arkivanov/decompose/router/items/Items;
+	public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/items/Items;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/items/Items;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getActiveItems ()Ljava/util/Map;
+	public final fun getItems ()Ljava/util/List;
+	public fun hashCode ()I
+	public fun toString ()Ljava/lang/String;
+}
+
+public synthetic class com/arkivanov/decompose/router/items/Items$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+	public fun <init> (Lkotlinx/serialization/KSerializer;)V
+	public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+	public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/arkivanov/decompose/router/items/Items;
+	public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+	public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+	public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/arkivanov/decompose/router/items/Items;)V
+	public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+	public final fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class com/arkivanov/decompose/router/items/Items$ActiveLifecycleState : java/lang/Enum {
+	public static final field CREATED Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+	public static final field RESUMED Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+	public static final field STARTED Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+	public static fun getEntries ()Lkotlin/enums/EnumEntries;
+	public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+	public static fun values ()[Lcom/arkivanov/decompose/router/items/Items$ActiveLifecycleState;
+}
+
+public final class com/arkivanov/decompose/router/items/Items$Companion {
+	public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
+}
+
+public abstract interface class com/arkivanov/decompose/router/items/ItemsNavigation : com/arkivanov/decompose/router/children/NavigationSource, com/arkivanov/decompose/router/items/ItemsNavigator {
+}
+
+public final class com/arkivanov/decompose/router/items/ItemsNavigation$Event {
+	public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+	public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final fun getOnComplete ()Lkotlin/jvm/functions/Function2;
+	public final fun getTransformer ()Lkotlin/jvm/functions/Function1;
+}
+
+public final class com/arkivanov/decompose/router/items/ItemsNavigationKt {
+	public static final fun ItemsNavigation ()Lcom/arkivanov/decompose/router/items/ItemsNavigation;
+}
+
+public abstract interface class com/arkivanov/decompose/router/items/ItemsNavigator {
+	public abstract fun navigate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+}
+
+public final class com/arkivanov/decompose/router/items/ItemsNavigatorExtKt {
+	public static final fun navigate (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;)V
+	public static final fun setActiveItems (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;)V
+	public static final fun setActiveItems (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+	public static synthetic fun setActiveItems$default (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+	public static final fun setItems (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;)V
+	public static final fun setItems (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+	public static synthetic fun setItems$default (Lcom/arkivanov/decompose/router/items/ItemsNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+}
+
+public abstract class com/arkivanov/decompose/router/items/LazyChildItems : com/arkivanov/decompose/value/Value, com/arkivanov/decompose/router/items/ItemsNavigator {
+	public fun <init> ()V
+	public abstract fun get (Ljava/lang/Object;)Ljava/lang/Object;
+	public abstract fun navigate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+}
+
 public final class com/arkivanov/decompose/router/pages/ChildPages {
 	public fun <init> ()V
 	public fun <init> (Ljava/util/List;I)V
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt
index 187c8a25..92d8c8c1 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt
@@ -25,3 +25,19 @@ internal fun <T : Any, C : Any> List<T>.keyed(configuration: (T) -> C): Map<Any,
         config to number
     }
 }
+
+internal fun <T : Any> Iterable<T>.findFirstDuplicate(set: Set<T>): Pair<Int, T>? {
+    val iter1 = iterator()
+    val iter2 = set.iterator()
+    var index = 0
+
+    while (iter1.hasNext()) {
+        val item = iter1.next()
+        if (!iter2.hasNext() || (iter2.next() != item)) {
+            return index to item
+        }
+        index++
+    }
+
+    return null
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/ChildBackHandler.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/ChildBackHandler.kt
index dd1f91c5..a927c83d 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/ChildBackHandler.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/ChildBackHandler.kt
@@ -9,6 +9,7 @@ import com.arkivanov.essenty.lifecycle.subscribe
 internal interface ChildBackHandler : BackHandler {
 
     var isEnabled: Boolean
+    var priority: Int
 
     fun start()
 
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandler.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandler.kt
index 851996e8..3948638c 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandler.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandler.kt
@@ -23,6 +23,7 @@ internal class DefaultChildBackHandler(
         )
 
     override var isEnabled: Boolean by observable(isEnabled) { _, _, _ -> updateParentCallbackEnabledState() }
+    override var priority: Int by parentCallback::priority
 
     init {
         delegate.addEnabledChangedListener { updateParentCallbackEnabledState() }
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildController.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildController.kt
new file mode 100644
index 00000000..4c421713
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildController.kt
@@ -0,0 +1,266 @@
+package com.arkivanov.decompose.router.children
+
+import com.arkivanov.decompose.GenericComponentContext
+import com.arkivanov.decompose.backhandler.ChildBackHandler
+import com.arkivanov.decompose.backhandler.child
+import com.arkivanov.decompose.backhandler.childBackHandler
+import com.arkivanov.decompose.lifecycle.MergedLifecycle
+import com.arkivanov.essenty.backhandler.BackCallback
+import com.arkivanov.essenty.instancekeeper.InstanceKeeper
+import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
+import com.arkivanov.essenty.instancekeeper.retainedInstance
+import com.arkivanov.essenty.lifecycle.Lifecycle
+import com.arkivanov.essenty.lifecycle.LifecycleRegistry
+import com.arkivanov.essenty.lifecycle.create
+import com.arkivanov.essenty.lifecycle.destroy
+import com.arkivanov.essenty.lifecycle.pause
+import com.arkivanov.essenty.lifecycle.resume
+import com.arkivanov.essenty.lifecycle.start
+import com.arkivanov.essenty.lifecycle.stop
+import com.arkivanov.essenty.statekeeper.SerializableContainer
+import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
+
+internal class ChildController<C : Any, out T : Any, out Ctx : GenericComponentContext<Ctx>>(
+    private val componentContext: Ctx,
+    key: Any,
+    private val childFactory: (C, Ctx) -> T,
+) {
+
+    private val retainedInstance =
+        componentContext.retainedInstance<RetainedInstance<C>>(key = key, factory = ::RetainedInstance)
+
+    private val childBackHandler = componentContext.backHandler.child(priority = BackCallback.PRIORITY_DEFAULT + 1)
+
+    private val items = HashMap<C, Item<T>>()
+
+    fun init(dropState: Boolean = false, block: () -> Unit = {}) {
+        if (dropState) {
+            retainedInstance.onDestroy()
+        }
+
+        block()
+
+        val iter = retainedInstance.map.iterator()
+        while (iter.hasNext()) {
+            val (cfg, instanceKeeper) = iter.next()
+
+            if (items[cfg]?.instance == null) {
+                instanceKeeper.destroy()
+                iter.remove()
+            }
+        }
+    }
+
+    fun saveState(configuration: C): SerializableContainer? =
+        items[configuration]?.saveState()
+
+    private fun Item<*>.saveState(): SerializableContainer? =
+        when (this) {
+            is Item.Created -> stateKeeperDispatcher.save()
+            is Item.Destroyed -> savedState
+        }
+
+    operator fun get(configuration: C): T? =
+        items[configuration]?.instance
+
+    fun getLifecycleState(configuration: C): Lifecycle.State? =
+        when (val item = items[configuration]) {
+            is Item.Created -> item.lifecycleRegistry.state
+            is Item.Destroyed -> Lifecycle.State.DESTROYED
+            null -> null
+        }
+
+    fun remove(configuration: C) {
+        val item = items.remove(configuration) as? Item.Created ?: return
+
+        item.destroy()
+        retainedInstance.map -= configuration
+    }
+
+    fun destroy(configuration: C, savedState: SerializableContainer? = null) {
+        val item = items.remove(configuration) as? Item.Created
+
+        if (item == null) {
+            items[configuration] = Item.Destroyed(savedState)
+            return
+        }
+
+        val childSavedState = savedState ?: item.stateKeeperDispatcher.save()
+        item.destroy()
+        retainedInstance.map -= configuration
+
+        items[configuration] = Item.Destroyed(childSavedState)
+    }
+
+    fun create(configuration: C, savedState: SerializableContainer? = null): T =
+        activate(
+            configuration = configuration,
+            lifecycleState = ActiveLifecycleState.CREATED,
+            savedState = savedState,
+        )
+
+    fun start(
+        configuration: C,
+        backHandlerPriority: Int = BackCallback.PRIORITY_DEFAULT,
+        savedState: SerializableContainer? = null
+    ): T =
+        activate(
+            configuration = configuration,
+            lifecycleState = ActiveLifecycleState.STARTED,
+            backHandlerPriority = backHandlerPriority,
+            savedState = savedState,
+        )
+
+    fun resume(
+        configuration: C,
+        backHandlerPriority: Int = BackCallback.PRIORITY_DEFAULT,
+        savedState: SerializableContainer? = null
+    ): T =
+        activate(
+            configuration = configuration,
+            lifecycleState = ActiveLifecycleState.RESUMED,
+            backHandlerPriority = backHandlerPriority,
+            savedState = savedState,
+        )
+
+    private fun activate(
+        configuration: C,
+        lifecycleState: ActiveLifecycleState,
+        backHandlerPriority: Int = BackCallback.PRIORITY_DEFAULT,
+        savedState: SerializableContainer? = null
+    ): T =
+        when (val oldItem = items[configuration]) {
+            is Item.Created -> oldItem.apply { setState(lifecycleState, backHandlerPriority) }.instance
+
+            is Item.Destroyed ->
+                activateNew(
+                    configuration,
+                    lifecycleState,
+                    backHandlerPriority,
+                    savedState ?: oldItem.savedState,
+                )
+
+            null -> activateNew(configuration, lifecycleState, backHandlerPriority, savedState)
+        }
+
+    private fun activateNew(
+        configuration: C,
+        lifecycleState: ActiveLifecycleState,
+        backHandlerPriority: Int = BackCallback.PRIORITY_DEFAULT,
+        savedState: SerializableContainer? = null,
+    ): T {
+        val item = item(configuration = configuration, savedState = savedState)
+        item.setState(lifecycleState = lifecycleState, backHandlerPriority = backHandlerPriority)
+        retainedInstance.map[configuration] = item.instanceKeeperDispatcher
+        items[configuration] = item
+
+        return item.instance
+    }
+
+    private fun Item.Created<*>.setState(lifecycleState: ActiveLifecycleState, backHandlerPriority: Int) {
+        lifecycleRegistry.setState(lifecycleState)
+        backHandler.setState(lifecycleState = lifecycleState, priority = backHandlerPriority)
+    }
+
+    private fun LifecycleRegistry.setState(lifecycleState: ActiveLifecycleState) {
+        when (lifecycleState) {
+            ActiveLifecycleState.CREATED ->
+                if (state < Lifecycle.State.CREATED) {
+                    create()
+                } else {
+                    stop()
+                }
+
+            ActiveLifecycleState.STARTED ->
+                if (state < Lifecycle.State.STARTED) {
+                    start()
+                } else {
+                    pause()
+                }
+
+            ActiveLifecycleState.RESUMED -> resume()
+        }
+    }
+
+    private fun ChildBackHandler.setState(
+        lifecycleState: ActiveLifecycleState,
+        priority: Int,
+    ) {
+        when (lifecycleState) {
+            ActiveLifecycleState.CREATED -> stop()
+            ActiveLifecycleState.STARTED,
+            ActiveLifecycleState.RESUMED -> start()
+        }
+
+        this@setState.priority = priority
+    }
+
+    private fun item(
+        configuration: C,
+        savedState: SerializableContainer?,
+    ): Item.Created<T> {
+        val componentLifecycleRegistry = LifecycleRegistry()
+        val mergedLifecycle = MergedLifecycle(componentContext.lifecycle, componentLifecycleRegistry)
+        val stateKeeperDispatcher = StateKeeperDispatcher(savedState)
+        val instanceKeeperDispatcher = retainedInstance.map[configuration] ?: InstanceKeeperDispatcher()
+        val backHandler = childBackHandler.childBackHandler()
+
+        val component =
+            childFactory(
+                configuration,
+                componentContext.componentContextFactory(
+                    lifecycle = mergedLifecycle,
+                    stateKeeper = stateKeeperDispatcher,
+                    instanceKeeper = instanceKeeperDispatcher,
+                    backHandler = backHandler,
+                )
+            )
+
+        return Item.Created(
+            instance = component,
+            lifecycleRegistry = componentLifecycleRegistry,
+            stateKeeperDispatcher = stateKeeperDispatcher,
+            instanceKeeperDispatcher = instanceKeeperDispatcher,
+            backHandler = backHandler,
+        )
+    }
+
+    private fun Item.Created<*>.destroy() {
+        backHandler.stop()
+        lifecycleRegistry.destroy()
+        instanceKeeperDispatcher.destroy()
+    }
+
+    private enum class ActiveLifecycleState {
+        CREATED,
+        STARTED,
+        RESUMED,
+    }
+
+    private sealed interface Item<out T : Any> {
+        val instance: T?
+
+        class Destroyed(
+            val savedState: SerializableContainer? = null,
+        ) : Item<Nothing> {
+            override val instance: Nothing? = null
+        }
+
+        class Created<out T : Any>(
+            override val instance: T,
+            val lifecycleRegistry: LifecycleRegistry,
+            val stateKeeperDispatcher: StateKeeperDispatcher,
+            val instanceKeeperDispatcher: InstanceKeeperDispatcher,
+            val backHandler: ChildBackHandler,
+        ) : Item<T>
+    }
+
+    private class RetainedInstance<C : Any> : InstanceKeeper.Instance {
+        val map: MutableMap<C, InstanceKeeperDispatcher> = HashMap()
+
+        override fun onDestroy() {
+            map.values.forEach { it.destroy() }
+            map.clear()
+        }
+    }
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildControllerExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildControllerExt.kt
new file mode 100644
index 00000000..344e63df
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildControllerExt.kt
@@ -0,0 +1,6 @@
+package com.arkivanov.decompose.router.children
+
+import com.arkivanov.essenty.lifecycle.Lifecycle
+
+internal fun <C : Any> ChildController<C, *, *>.isActive(configuration: C): Boolean =
+    getLifecycleState(configuration)?.takeIf { it > Lifecycle.State.DESTROYED } != null
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavStateSaver.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavStateSaver.kt
new file mode 100644
index 00000000..1495bff1
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavStateSaver.kt
@@ -0,0 +1,54 @@
+package com.arkivanov.decompose.router.children
+
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.essenty.statekeeper.SerializableContainer
+import kotlinx.serialization.KSerializer
+
+
+/**
+ * A contract for saving and restoring navigation states.
+ */
+@ExperimentalDecomposeApi
+interface NavStateSaver<T : Any> {
+
+    /**
+     * Saves the provided navigation state into [SerializableContainer].
+     *
+     * @param state the navigation state to be saved
+     * @return a serializable container that holds the saved state, or `null` if the state should not be saved.
+     */
+    fun saveState(state: T): SerializableContainer?
+
+    /**
+     * Restores the previously saved navigation state from the provided [SerializableContainer].
+     *
+     * @param container the serializable container holding the previously saved state.
+     * @return the restored navigation state, or `null` if the state could not be restored.
+     */
+    fun restoreState(container: SerializableContainer): T?
+}
+
+/**
+ * A convenience function for creating an instance of [NavStateSaver][com.arkivanov.decompose.router.children.NavStateSaver]
+ * with the provided [save] and [restore] functions.
+ */
+@ExperimentalDecomposeApi
+inline fun <T : Any> NavStateSaver(
+    crossinline save: (T) -> SerializableContainer?,
+    crossinline restore: (SerializableContainer) -> T?,
+): NavStateSaver<T> =
+    object : NavStateSaver<T> {
+        override fun saveState(state: T): SerializableContainer? = save(state)
+        override fun restoreState(container: SerializableContainer): T? = restore(container)
+    }
+
+/**
+ * A convenience function for creating an instance of [NavStateSaver][com.arkivanov.decompose.router.children.NavStateSaver]
+ * that saves and restores the navigation state using the provided [serializer].
+ */
+@ExperimentalDecomposeApi
+fun <T : Any> NavStateSaver(serializer: KSerializer<T>): NavStateSaver<T> =
+    NavStateSaver(
+        save = { SerializableContainer(value = it, strategy = serializer) },
+        restore = { it.consume(strategy = serializer) },
+    )
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/TransientNavStateSaver.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/TransientNavStateSaver.kt
new file mode 100644
index 00000000..a17bc512
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/TransientNavStateSaver.kt
@@ -0,0 +1,29 @@
+package com.arkivanov.decompose.router.children
+
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.essenty.statekeeper.SerializableContainer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
+
+/**
+ * Creates an instance of [NavStateSaver] that does not serialize the navigation state.
+ * The navigation state is preserved over configuration changes on Android
+ * but is lost when the application process is recreated.
+ *
+ * Can be used for large navigation states like lists to avoid exceeding the Bundle size limit
+ * when saving the state.
+ */
+@ExperimentalDecomposeApi
+fun <T : Any> transientNavStateSaver(): NavStateSaver<T> =
+    NavStateSaver(
+        save = { SerializableContainer(value = TransientSavedState(it), strategy = TransientSavedState.serializer()) },
+        restore = {
+            @Suppress("UNCHECKED_CAST")
+            it.consume(TransientSavedState.serializer())?.value as T?
+        },
+    )
+
+@Serializable
+private class TransientSavedState(
+    @Transient val value: Any? = null,
+)
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ChildItems.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ChildItems.kt
new file mode 100644
index 00000000..579bc037
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ChildItems.kt
@@ -0,0 +1,17 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+
+/**
+ * A state holder for Child Items.
+ *
+ * @param items a list of child configurations, can be empty.
+ * @param activeItems a map of instantiated child components and their lifecycle states.
+ * See [ActiveLifecycleState].
+ */
+@ExperimentalDecomposeApi
+data class ChildItems<C : Any, out T : Any>(
+    val items: List<C> = emptyList(),
+    val activeItems: Map<C, Pair<T, ActiveLifecycleState>> = emptyMap(),
+)
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ChildItemsFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ChildItemsFactory.kt
new file mode 100644
index 00000000..62986280
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ChildItemsFactory.kt
@@ -0,0 +1,91 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.GenericComponentContext
+import com.arkivanov.decompose.router.children.ChildController
+import com.arkivanov.decompose.router.children.NavStateSaver
+import com.arkivanov.decompose.router.children.NavigationSource
+import com.arkivanov.decompose.router.items.ItemsNavigation.Event
+import com.arkivanov.essenty.lifecycle.doOnDestroy
+import kotlinx.serialization.KSerializer
+
+
+/**
+ * Initializes and manages a list of components with arbitrary lifecycle states. Typically used for lazy lists.
+ *
+ * **It is strongly recommended to call this method on the Main thread.**
+ *
+ * @param source a source of navigation events.
+ * @param serializer an optional [KSerializer] for serializing and deserializing configurations.
+ * If `null` then the navigation state will not be preserved.
+ * @param initialItems an initial state of Child Items that should be set
+ * if there is no saved state. See [Items] for more information.
+ * @param key a key of the navigation, must be unique if there are multiple Child Items used in
+ * the same component.
+ * @param childFactory a factory function that creates new child instances.
+ * @return observable [LazyChildItems].
+ */
+@ExperimentalDecomposeApi
+fun <Ctx : GenericComponentContext<Ctx>, C : Any, T : Any> Ctx.childItems(
+    source: NavigationSource<Event<C>>,
+    serializer: KSerializer<C>?,
+    initialItems: () -> Items<C>,
+    key: String = "DefaultChildItems",
+    childFactory: (configuration: C, Ctx) -> T,
+): LazyChildItems<C, T> =
+    childItems(
+        source = source,
+        initialItems = initialItems,
+        stateSaver = serializer?.let { NavStateSaver(Items.serializer(it)) },
+        key = key,
+        childFactory = childFactory,
+    )
+
+/**
+ * Initializes and manages a list of components with arbitrary lifecycle states. Typically used for lazy lists.
+ *
+ * **It is strongly recommended to call this method on the Main thread.**
+ *
+ * @param source a source of navigation events.
+ * @param stateSaver an optional [NavStateSaver] for saving and restoring the navigation state.
+ * If `null` then the navigation state will not be preserved.
+ * Use [transientNavStateSaver][com.arkivanov.decompose.router.children.transientNavStateSaver]
+ * to prevent the navigation state from being saved to disk and only keep it in memory (i.e., saved
+ * only over configuration changes on Android).
+ * @param initialItems an initial state of Child Items that should be set
+ * if there is no saved state. See [Items] for more information.
+ * @param key a key of the navigation, must be unique if there are multiple Child Items used in
+ * the same component.
+ * @param childFactory a factory function that creates new child instances.
+ * @return observable [LazyChildItems].
+ */
+@ExperimentalDecomposeApi
+fun <Ctx : GenericComponentContext<Ctx>, C : Any, T : Any> Ctx.childItems(
+    source: NavigationSource<Event<C>>,
+    stateSaver: NavStateSaver<Items<C>>?,
+    initialItems: () -> Items<C>,
+    key: String = "DefaultChildItems",
+    childFactory: (configuration: C, Ctx) -> T,
+): LazyChildItems<C, T> {
+    val navigator =
+        ItemsController(
+            controller = ChildController(
+                componentContext = this,
+                key = key,
+                childFactory = childFactory,
+            ),
+        )
+
+    val cancellation =
+        navigator.init(
+            source = source,
+            initialState = initialItems,
+            key = key,
+            stateKeeper = stateKeeper,
+            stateSaver = stateSaver,
+        )
+
+    lifecycle.doOnDestroy(cancellation::cancel)
+
+    return DefaultLazyChildItems(navigator)
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/DefaultItemsNavigation.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/DefaultItemsNavigation.kt
new file mode 100644
index 00000000..b34793dd
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/DefaultItemsNavigation.kt
@@ -0,0 +1,20 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.Cancellation
+import com.arkivanov.decompose.Relay
+import com.arkivanov.decompose.router.items.ItemsNavigation.Event
+
+internal class DefaultItemsNavigation<C : Any> : ItemsNavigation<C> {
+
+    private val relay = Relay<Event<C>>()
+
+    override fun subscribe(observer: (Event<C>) -> Unit): Cancellation =
+        relay.subscribe(observer)
+
+    override fun navigate(
+        transformer: (Items<C>) -> Items<C>,
+        onComplete: (newItems: Items<C>, oldItems: Items<C>) -> Unit,
+    ) {
+        relay.accept(Event(transformer, onComplete))
+    }
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/DefaultLazyChildItems.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/DefaultLazyChildItems.kt
new file mode 100644
index 00000000..fcb8a657
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/DefaultLazyChildItems.kt
@@ -0,0 +1,29 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.Cancellation
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+
+internal class DefaultLazyChildItems<C : Any, T : Any>(
+    private val controller: ItemsController<C, T>,
+) : LazyChildItems<C, T>(), ItemsNavigator<C> by controller {
+    override val value: ChildItems<C, T> get() = controller.state.value
+
+    override fun subscribe(observer: (ChildItems<C, T>) -> Unit): Cancellation =
+        controller.state.subscribe(observer)
+
+    override fun get(configuration: C): T {
+        var item = value.activeItems[configuration]
+        if (item == null) {
+            setActiveItems { it + (configuration to ActiveLifecycleState.CREATED) }
+            item = value.activeItems[configuration]
+        }
+
+        checkNotNull(item) {
+            "Component was not created for $configuration. " +
+                "Make sure you are not calling get(...) when navigation is already in progress (e.g. recursively). " +
+                "Also make sure you are not removing/destroying the component during its instantiation."
+        }
+
+        return item.first
+    }
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/Items.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/Items.kt
new file mode 100644
index 00000000..6c701c39
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/Items.kt
@@ -0,0 +1,28 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import kotlinx.serialization.Serializable
+
+/**
+ * Represents a state of Child Items navigation model.
+ *
+ * @param items a list of child configurations, can be empty. Must be unique even if the
+ * [com.arkivanov.decompose.DecomposeExperimentFlags.duplicateConfigurationsEnabled] flag is enabled.
+ * @param activeItems a map of lifecycle states of the instantiated (active) components.
+ * Child components whose configurations are not present in this map are destroyed.
+ * Configurations in the map should also be present in the [items] list,
+ * otherwise the behavior is undefined.
+ * See [ActiveLifecycleState].
+ */
+@ExperimentalDecomposeApi
+@Serializable
+data class Items<C : Any>(
+    val items: List<C> = emptyList(),
+    val activeItems: Map<C, ActiveLifecycleState> = emptyMap(),
+) {
+    enum class ActiveLifecycleState {
+        CREATED,
+        STARTED,
+        RESUMED,
+    }
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsController.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsController.kt
new file mode 100644
index 00000000..14df9505
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsController.kt
@@ -0,0 +1,168 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.Cancellation
+import com.arkivanov.decompose.Relay
+import com.arkivanov.decompose.findFirstDuplicate
+import com.arkivanov.decompose.router.children.ChildController
+import com.arkivanov.decompose.router.children.NavStateSaver
+import com.arkivanov.decompose.router.children.NavigationSource
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+import com.arkivanov.decompose.router.items.ItemsNavigation.Event
+import com.arkivanov.decompose.value.MutableValue
+import com.arkivanov.decompose.value.Value
+import com.arkivanov.essenty.statekeeper.SerializableContainer
+import com.arkivanov.essenty.statekeeper.StateKeeper
+import kotlinx.serialization.Serializable
+
+internal class ItemsController<C : Any, out T : Any>(
+    private val controller: ChildController<C, T, *>,
+) : ItemsNavigator<C> {
+
+    private val nav = Relay<NavEvent<C>>()
+    private val navState = MutableValue(Items<C>())
+
+    private val _state = MutableValue(ChildItems<C, T>())
+    val state: Value<ChildItems<C, T>> = _state
+
+    fun init(
+        source: NavigationSource<Event<C>>,
+        initialState: () -> Items<C>,
+        key: String,
+        stateKeeper: StateKeeper,
+        stateSaver: NavStateSaver<Items<C>>?,
+    ): Cancellation {
+        val restoredState = stateKeeper.consume(key = key, strategy = SavedState.serializer())
+        val restoredNavState = restoredState?.let { stateSaver?.restoreState(it.navState) }
+
+        if (stateSaver != null) {
+            stateKeeper.register(key = key, SavedState.serializer()) {
+                stateSaver.saveState(navState.value)?.let { navState ->
+                    SavedState(navState = navState, childState = saveChildState())
+                }
+            }
+        }
+
+        nav.subscribe(::onEvent)
+        val cancellation = source.subscribe { nav.accept(NavEvent.Event(it)) }
+
+        nav.accept(
+            NavEvent.Init(
+                initialState = restoredNavState ?: initialState(),
+                savedChildState = restoredState?.childState?.takeUnless { restoredNavState == null },
+            ),
+        )
+
+        return cancellation
+    }
+
+    override fun navigate(transformer: (Items<C>) -> Items<C>, onComplete: (Items<C>, Items<C>) -> Unit) {
+        nav.accept(NavEvent.Event(Event(transformer, onComplete)))
+    }
+
+    private fun saveChildState(): Map<Int, SerializableContainer> {
+        val childState = HashMap<Int, SerializableContainer>()
+
+        navState.value.items.forEachIndexed { index, cfg ->
+            controller.saveState(cfg)?.also {
+                childState[index] = it
+            }
+        }
+
+        return childState
+    }
+
+    private fun onEvent(event: NavEvent<C>) {
+        when (event) {
+            is NavEvent.Init -> onInit(event)
+            is NavEvent.Event -> onEvent(event.event)
+        }
+    }
+
+    private fun onInit(event: NavEvent.Init<C>) {
+        val initialNavState = event.initialState
+        val savedChildState = event.savedChildState
+        val activeItems = HashMap<C, Pair<T, ActiveLifecycleState>>()
+
+        controller.init(dropState = savedChildState == null) {
+            initialNavState.items.forEachIndexed { index, cfg ->
+                val childSavedState = savedChildState?.get(index)
+                val lifecycleState = initialNavState.activeItems[cfg]
+                if (lifecycleState != null) {
+                    val instance = activateChild(cfg, lifecycleState, childSavedState)
+                    activeItems[cfg] = instance to lifecycleState
+                } else {
+                    controller.destroy(cfg, childSavedState)
+                }
+            }
+        }
+
+        setState(navState = initialNavState, activeItems = activeItems)
+    }
+
+    private fun setState(navState: Items<C>, activeItems: Map<C, Pair<T, ActiveLifecycleState>>) {
+        this.navState.value = navState
+        _state.value = ChildItems(items = navState.items, activeItems = activeItems)
+    }
+
+    private fun onEvent(event: Event<C>) {
+        val oldNavState = navState.value
+        var newNavState = event.transformer(oldNavState)
+
+        val newConfigs = newNavState.items.takeUnless { it === oldNavState.items }?.toSet()
+
+        if (newConfigs != null) {
+            check(newConfigs.size == newNavState.items.size) {
+                val diff = newNavState.items.findFirstDuplicate(newConfigs)
+                "Configurations must be unique. " +
+                    "First duplicate: ${diff?.second} at index ${diff?.first}. List size: ${newNavState.items.size}."
+            }
+
+            val extraActiveItems = newNavState.activeItems.keys.filterNotTo(HashSet()) { it in newConfigs }
+            if (extraActiveItems.isNotEmpty()) {
+                newNavState = newNavState.copy(activeItems = newNavState.activeItems - extraActiveItems)
+            }
+        }
+
+        val newActiveItems =
+            newNavState.activeItems.mapValues { (cfg, lifecycleState) ->
+                val instance = activateChild(cfg, lifecycleState)
+                instance to lifecycleState
+            }
+
+        oldNavState.activeItems.forEach { (cfg) ->
+            if (cfg !in newNavState.activeItems) {
+                val isExistingConfig = (newConfigs == null) || (cfg in newConfigs)
+                if (isExistingConfig) {
+                    controller.destroy(configuration = cfg)
+                } else {
+                    controller.remove(configuration = cfg)
+                }
+            }
+        }
+
+        setState(navState = newNavState, activeItems = newActiveItems)
+        event.onComplete(newNavState, oldNavState)
+    }
+
+    private fun activateChild(configuration: C, lifecycleState: ActiveLifecycleState, savedState: SerializableContainer? = null): T =
+        when (lifecycleState) {
+            ActiveLifecycleState.CREATED -> controller.create(configuration = configuration, savedState = savedState)
+            ActiveLifecycleState.STARTED -> controller.start(configuration = configuration, savedState = savedState)
+            ActiveLifecycleState.RESUMED -> controller.resume(configuration = configuration, savedState = savedState)
+        }
+
+    @Serializable
+    private class SavedState(
+        val navState: SerializableContainer,
+        val childState: Map<Int, SerializableContainer>,
+    )
+
+    private sealed interface NavEvent<C : Any> {
+        class Init<C : Any>(
+            val initialState: Items<C>,
+            val savedChildState: Map<Int, SerializableContainer>?,
+        ) : NavEvent<C>
+
+        class Event<C : Any>(val event: ItemsNavigation.Event<C>) : NavEvent<C>
+    }
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsNavigation.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsNavigation.kt
new file mode 100644
index 00000000..e439ef27
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsNavigation.kt
@@ -0,0 +1,25 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.router.children.NavigationSource
+import com.arkivanov.decompose.router.items.ItemsNavigation.Event
+
+/**
+ * Represents [ItemsNavigator] and [NavigationSource] at the same time.
+ */
+@ExperimentalDecomposeApi
+interface ItemsNavigation<C : Any> : ItemsNavigator<C>, NavigationSource<Event<C>> {
+
+    class Event<C : Any>(
+        val transformer: (Items<C>) -> Items<C>,
+        val onComplete: (newItems: Items<C>, oldItems: Items<C>) -> Unit = { _, _ -> },
+    )
+}
+
+/**
+ * Returns a default implementation of [ItemsNavigation].
+ * Broadcasts navigation events to all subscribed observers.
+ */
+@ExperimentalDecomposeApi
+fun <C : Any> ItemsNavigation(): ItemsNavigation<C> =
+    DefaultItemsNavigation()
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsNavigator.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsNavigator.kt
new file mode 100644
index 00000000..a00c2ac8
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsNavigator.kt
@@ -0,0 +1,31 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+
+@ExperimentalDecomposeApi
+interface ItemsNavigator<C : Any> {
+
+    /**
+     * Transforms the current [Items] state to a new one.
+     *
+     * During the navigation process, the Child Items navigation model compares the new [Items] state
+     * with the previous one. The navigation model ensures that all deactivated components are destroyed,
+     * and updates lifecycles of the active components to match the new state.
+     *
+     * The navigation is usually performed synchronously, which means that by the time
+     * the `navigate` method returns, the navigation is finished and all component lifecycles are
+     * moved into required states. However, the navigation is performed asynchronously in case of
+     * recursive invocations - e.g. `setActiveItems` is called from `onCreate` lifecycle callback of a
+     * component being activated. All recursive invocations are queued and performed one by one once
+     * the current navigation is finished.
+     *
+     * Should be called on the main thread.
+     *
+     * @param transformer transforms the current [Items] state to a new one.
+     * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
+     */
+    fun navigate(
+        transformer: (Items<C>) -> Items<C>,
+        onComplete: (newItems: Items<C>, oldItems: Items<C>) -> Unit,
+    )
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsNavigatorExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsNavigatorExt.kt
new file mode 100644
index 00000000..6f32157a
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ItemsNavigatorExt.kt
@@ -0,0 +1,80 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+
+/**
+ * A convenience method for [ItemsNavigator.navigate].
+ */
+@ExperimentalDecomposeApi
+fun <C : Any> ItemsNavigator<C>.navigate(transformer: (Items<C>) -> Items<C>) {
+    navigate(transformer = transformer, onComplete = { _, _ -> })
+}
+
+/**
+ * Replaces the items with the provided list.
+ *
+ * @param items a transformer function from the current item list to a new one.
+ * See [Items.items].
+ * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
+ */
+@ExperimentalDecomposeApi
+inline fun <C : Any> ItemsNavigator<C>.setItems(
+    crossinline items: (List<C>) -> List<C>,
+    crossinline onComplete: (newItems: Items<C>, oldItems: Items<C>) -> Unit = { _, _ -> },
+) {
+    navigate(
+        transformer = { oldItems ->
+            val newItems = items(oldItems.items)
+            oldItems.copy(
+                items = newItems,
+                activeItems = oldItems.activeItems.toMutableMap().apply { keys.retainAll(newItems.toSet()) },
+            )
+        },
+        onComplete = { newItems, oldItems -> onComplete(newItems, oldItems) },
+    )
+}
+
+/**
+ * Replaces the items with the provided list.
+ *
+ * @param items a transformer function from the current item list to a new one.
+ * See [Items.items].
+ */
+@ExperimentalDecomposeApi
+fun <C : Any> ItemsNavigator<C>.setItems(items: (List<C>) -> List<C>) {
+    setItems(items = items, onComplete = { _, _ -> })
+}
+
+/**
+ * Sets the currently instantiated (active) items with the provided lifecycle states.
+ * See [ActiveLifecycleState].
+ *
+ * @param activeItems a transformer function from the currently active items map to a new one.
+ * See [Items.activeItems].
+ * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
+ */
+@ExperimentalDecomposeApi
+inline fun <C : Any> ItemsNavigator<C>.setActiveItems(
+    crossinline activeItems: (Map<C, ActiveLifecycleState>) -> Map<C, ActiveLifecycleState>,
+    crossinline onComplete: (newItems: Items<C>, oldItems: Items<C>) -> Unit = { _, _ -> },
+) {
+    navigate(
+        transformer = { it.copy(activeItems = activeItems(it.activeItems)) },
+        onComplete = { newItems, oldItems -> onComplete(newItems, oldItems) },
+    )
+}
+
+/**
+ * Sets the currently instantiated (active) items with the provided lifecycle states.
+ * See [ActiveLifecycleState].
+ *
+ * @param activeItems a transformer function from the currently active items map to a new one.
+ * See [Items.activeItems].
+ */
+@ExperimentalDecomposeApi
+inline fun <C : Any> ItemsNavigator<C>.setActiveItems(
+    crossinline activeItems: (Map<C, ActiveLifecycleState>) -> Map<C, ActiveLifecycleState>,
+) {
+    setActiveItems(activeItems = activeItems, onComplete = { _, _ -> })
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/LazyChildItems.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/LazyChildItems.kt
new file mode 100644
index 00000000..bfdbc15a
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/LazyChildItems.kt
@@ -0,0 +1,27 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.value.Value
+
+/**
+ * Represents an observable [Value] of [ChildItems] and [ItemsNavigator] at the same time.
+ * Allows lazy instantiation of the child components.
+ */
+@ExperimentalDecomposeApi
+abstract class LazyChildItems<C : Any, out T : Any> : Value<ChildItems<C, T>>(), ItemsNavigator<C> {
+
+    /**
+     * Returns an instance of the child component corresponding to the provided [configuration],
+     * creating it if needed.
+     *
+     * Please note that this function must not be called recursively during navigation, i.e., when
+     * a child component is being instantiated. Additionally, the component being created must not
+     * remove itself from the list during the navigation process. An [IllegalStateException] will
+     * be thrown if the component couldn't be created.
+     *
+     * Should be called on the main thread.
+     */
+    abstract operator fun get(configuration: C): T
+
+    abstract override fun navigate(transformer: (Items<C>) -> Items<C>, onComplete: (Items<C>, Items<C>) -> Unit)
+}
diff --git a/extensions-compose/api/android/extensions-compose.api b/extensions-compose/api/android/extensions-compose.api
index 51c5f9a4..a7ff4388 100644
--- a/extensions-compose/api/android/extensions-compose.api
+++ b/extensions-compose/api/android/extensions-compose.api
@@ -2,6 +2,11 @@ public final class com/arkivanov/decompose/extensions/compose/SubscribeAsStateKt
 	public static final fun subscribeAsState (Lcom/arkivanov/decompose/value/Value;Landroidx/compose/runtime/SnapshotMutationPolicy;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
 }
 
+public final class com/arkivanov/decompose/extensions/compose/lazyitems/ChildItemsLifecycleControllerKt {
+	public static final fun ChildItemsLifecycleController (Lcom/arkivanov/decompose/router/items/LazyChildItems;Landroidx/compose/foundation/lazy/LazyListState;Lkotlin/jvm/functions/Function1;IILandroidx/compose/runtime/Composer;II)V
+	public static final fun ChildItemsLifecycleController (Lcom/arkivanov/decompose/router/items/LazyChildItems;Landroidx/compose/foundation/lazy/grid/LazyGridState;Lkotlin/jvm/functions/Function1;IILandroidx/compose/runtime/Composer;II)V
+}
+
 public final class com/arkivanov/decompose/extensions/compose/pages/ChildPagesKt {
 	public static final fun ChildPages (Lcom/arkivanov/decompose/router/pages/ChildPages;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/pages/PagesScrollAnimation;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;II)V
 	public static final fun ChildPages (Lcom/arkivanov/decompose/value/Value;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/pages/PagesScrollAnimation;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;II)V
diff --git a/extensions-compose/api/extensions-compose.klib.api b/extensions-compose/api/extensions-compose.klib.api
index 66899afc..bf26dfee 100644
--- a/extensions-compose/api/extensions-compose.klib.api
+++ b/extensions-compose/api/extensions-compose.klib.api
@@ -95,6 +95,8 @@ final fun <#A: kotlin/Any, #B: kotlin/Any> com.arkivanov.decompose.extensions.co
 final fun <#A: kotlin/Any, #B: kotlin/Any> com.arkivanov.decompose.extensions.compose.stack/Children(com.arkivanov.decompose.router.stack/ChildStack<#A, #B>, androidx.compose.ui/Modifier?, com.arkivanov.decompose.extensions.compose.stack.animation/StackAnimation<#A, #B>?, kotlin/Function3<com.arkivanov.decompose/Child.Created<#A, #B>, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.stack/Children|Children(com.arkivanov.decompose.router.stack.ChildStack<0:0,0:1>;androidx.compose.ui.Modifier?;com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation<0:0,0:1>?;kotlin.Function3<com.arkivanov.decompose.Child.Created<0:0,0:1>,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§<kotlin.Any>;1§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any, #B: kotlin/Any> com.arkivanov.decompose.extensions.compose.stack/Children(com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.stack/ChildStack<#A, #B>>, androidx.compose.ui/Modifier?, com.arkivanov.decompose.extensions.compose.stack.animation/StackAnimation<#A, #B>?, kotlin/Function3<com.arkivanov.decompose/Child.Created<#A, #B>, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.stack/Children|Children(com.arkivanov.decompose.value.Value<com.arkivanov.decompose.router.stack.ChildStack<0:0,0:1>>;androidx.compose.ui.Modifier?;com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation<0:0,0:1>?;kotlin.Function3<com.arkivanov.decompose.Child.Created<0:0,0:1>,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§<kotlin.Any>;1§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any> (com.arkivanov.decompose.value/Value<#A>).com.arkivanov.decompose.extensions.compose/subscribeAsState(androidx.compose.runtime/SnapshotMutationPolicy<#A>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int): androidx.compose.runtime/State<#A> // com.arkivanov.decompose.extensions.compose/subscribeAsState|subscribeAsState@com.arkivanov.decompose.value.Value<0:0>(androidx.compose.runtime.SnapshotMutationPolicy<0:0>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§<kotlin.Any>}[0]
+final fun <#A: kotlin/Any> com.arkivanov.decompose.extensions.compose.lazyitems/ChildItemsLifecycleController(com.arkivanov.decompose.router.items/LazyChildItems<#A, *>, androidx.compose.foundation.lazy.grid/LazyGridState, kotlin/Function1<kotlin/Int, kotlin/Int>, kotlin/Int, kotlin/Int, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.lazyitems/ChildItemsLifecycleController|ChildItemsLifecycleController(com.arkivanov.decompose.router.items.LazyChildItems<0:0,*>;androidx.compose.foundation.lazy.grid.LazyGridState;kotlin.Function1<kotlin.Int,kotlin.Int>;kotlin.Int;kotlin.Int;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§<kotlin.Any>}[0]
+final fun <#A: kotlin/Any> com.arkivanov.decompose.extensions.compose.lazyitems/ChildItemsLifecycleController(com.arkivanov.decompose.router.items/LazyChildItems<#A, *>, androidx.compose.foundation.lazy/LazyListState, kotlin/Function1<kotlin/Int, kotlin/Int>, kotlin/Int, kotlin/Int, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.lazyitems/ChildItemsLifecycleController|ChildItemsLifecycleController(com.arkivanov.decompose.router.items.LazyChildItems<0:0,*>;androidx.compose.foundation.lazy.LazyListState;kotlin.Function1<kotlin.Int,kotlin.Int>;kotlin.Int;kotlin.Int;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§<kotlin.Any>}[0]
 final fun com.arkivanov.decompose.extensions.compose.pages/com_arkivanov_decompose_extensions_compose_pages_PagesScrollAnimation_Custom$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.pages/com_arkivanov_decompose_extensions_compose_pages_PagesScrollAnimation_Custom$stableprop_getter|com_arkivanov_decompose_extensions_compose_pages_PagesScrollAnimation_Custom$stableprop_getter(){}[0]
 final fun com.arkivanov.decompose.extensions.compose.pages/com_arkivanov_decompose_extensions_compose_pages_PagesScrollAnimation_Default$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.pages/com_arkivanov_decompose_extensions_compose_pages_PagesScrollAnimation_Default$stableprop_getter|com_arkivanov_decompose_extensions_compose_pages_PagesScrollAnimation_Default$stableprop_getter(){}[0]
 final fun com.arkivanov.decompose.extensions.compose.pages/com_arkivanov_decompose_extensions_compose_pages_PagesScrollAnimation_Disabled$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.pages/com_arkivanov_decompose_extensions_compose_pages_PagesScrollAnimation_Disabled$stableprop_getter|com_arkivanov_decompose_extensions_compose_pages_PagesScrollAnimation_Disabled$stableprop_getter(){}[0]
diff --git a/extensions-compose/api/jvm/extensions-compose.api b/extensions-compose/api/jvm/extensions-compose.api
index 24405aa1..a1cc8654 100644
--- a/extensions-compose/api/jvm/extensions-compose.api
+++ b/extensions-compose/api/jvm/extensions-compose.api
@@ -2,6 +2,11 @@ public final class com/arkivanov/decompose/extensions/compose/SubscribeAsStateKt
 	public static final fun subscribeAsState (Lcom/arkivanov/decompose/value/Value;Landroidx/compose/runtime/SnapshotMutationPolicy;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
 }
 
+public final class com/arkivanov/decompose/extensions/compose/lazyitems/ChildItemsLifecycleControllerKt {
+	public static final fun ChildItemsLifecycleController (Lcom/arkivanov/decompose/router/items/LazyChildItems;Landroidx/compose/foundation/lazy/LazyListState;Lkotlin/jvm/functions/Function1;IILandroidx/compose/runtime/Composer;II)V
+	public static final fun ChildItemsLifecycleController (Lcom/arkivanov/decompose/router/items/LazyChildItems;Landroidx/compose/foundation/lazy/grid/LazyGridState;Lkotlin/jvm/functions/Function1;IILandroidx/compose/runtime/Composer;II)V
+}
+
 public final class com/arkivanov/decompose/extensions/compose/lifecycle/LifecycleControllerKt {
 	public static final fun LifecycleController (Lcom/arkivanov/essenty/lifecycle/LifecycleRegistry;Landroidx/compose/ui/window/WindowState;Landroidx/compose/ui/platform/WindowInfo;Landroidx/compose/runtime/Composer;II)V
 }
diff --git a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/lazyitems/ChildItemsLifecycleController.kt b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/lazyitems/ChildItemsLifecycleController.kt
new file mode 100644
index 00000000..2d69ddb7
--- /dev/null
+++ b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/lazyitems/ChildItemsLifecycleController.kt
@@ -0,0 +1,139 @@
+package com.arkivanov.decompose.extensions.compose.lazyitems
+
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.extensions.compose.subscribeAsState
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+import com.arkivanov.decompose.router.items.LazyChildItems
+import com.arkivanov.decompose.router.items.setActiveItems
+
+
+/**
+ * Controls the lifecycle states of child items (components) in a lazy list
+ * (e.g. `LazyColumn` or `LazyRow`).
+ * The items within the viewport are moved to `RESUMED` state.
+ * The items outside the viewport but within the preloading delta are moved to `CREATED` state.
+ * The items outside the viewport and outside the preloading delta are destroyed.
+ *
+ * @param C A type of configuration associated with each child item (component).
+ * @param items A lazy collection of items, see [LazyChildItems].
+ * @param lazyListState The state of the lazy list, used to determine visible items. See [LazyListState].
+ * @param itemIndexConverter A function to map screen indices to indices in the [items] collection.
+ * Usually it should return the input value minus the number of header items (if there are any).
+ * @param forwardPreloadCount The number of items to preload ahead of the viewport.
+ * @param backwardPreloadCount The number of items to preload before the viewport.
+ */
+@ExperimentalDecomposeApi
+@Composable
+fun <C : Any> ChildItemsLifecycleController(
+    items: LazyChildItems<C, *>,
+    lazyListState: LazyListState,
+    itemIndexConverter: (Int) -> Int,
+    forwardPreloadCount: Int = 0,
+    backwardPreloadCount: Int = 0,
+) {
+    val firstIndex by remember(lazyListState) {
+        derivedStateOf {
+            lazyListState.layoutInfo.visibleItemsInfo.firstOrNull()?.index ?: -1
+        }
+    }
+
+    val lastIndex by remember(lazyListState) {
+        derivedStateOf {
+            lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
+        }
+    }
+
+    ChildItemsLifecycleController(
+        items = items,
+        backwardPreloadCount = backwardPreloadCount,
+        forwardPreloadCount = forwardPreloadCount,
+        itemIndexConverter = itemIndexConverter,
+        firstIndex = firstIndex,
+        lastIndex = lastIndex,
+    )
+}
+
+/**
+ * Controls the lifecycle states of child items (components) in a lazy grid
+ * (e.g. `LazyVerticalGrid` or `LazyHorizontalGrid`).
+ * The items within the viewport are moved to `RESUMED` state.
+ * The items outside the viewport but within the preloading delta are moved to `CREATED` state.
+ * The items outside the viewport and outside the preloading delta are destroyed.
+ *
+ * @param C A type of configuration associated with each child item (component).
+ * @param items A lazy collection of items, see [LazyChildItems].
+ * @param lazyGridState The state of the lazy grid, used to determine visible items. See [LazyGridState].
+ * @param itemIndexConverter A function to map screen indices to indices in the [items] collection.
+ * Usually it should return the input value minus the number of header items (if there are any).
+ * @param forwardPreloadCount The number of items to preload ahead of the viewport.
+ * @param backwardPreloadCount The number of items to preload before the viewport.
+ */
+@ExperimentalDecomposeApi
+@Composable
+fun <C : Any> ChildItemsLifecycleController(
+    items: LazyChildItems<C, *>,
+    lazyGridState: LazyGridState,
+    itemIndexConverter: (Int) -> Int,
+    forwardPreloadCount: Int = 0,
+    backwardPreloadCount: Int = 0,
+) {
+    val firstIndex by remember(lazyGridState) {
+        derivedStateOf {
+            lazyGridState.layoutInfo.visibleItemsInfo.firstOrNull()?.index ?: -1
+        }
+    }
+
+    val lastIndex by remember(lazyGridState) {
+        derivedStateOf {
+            lazyGridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
+        }
+    }
+
+    ChildItemsLifecycleController(
+        items = items,
+        backwardPreloadCount = backwardPreloadCount,
+        forwardPreloadCount = forwardPreloadCount,
+        itemIndexConverter = itemIndexConverter,
+        firstIndex = firstIndex,
+        lastIndex = lastIndex,
+    )
+}
+
+@Composable
+private fun <C : Any> ChildItemsLifecycleController(
+    items: LazyChildItems<C, *>,
+    backwardPreloadCount: Int,
+    forwardPreloadCount: Int,
+    itemIndexConverter: (Int) -> Int,
+    firstIndex: Int,
+    lastIndex: Int
+) {
+    val childItems by items.subscribeAsState()
+    val configurations = childItems.items
+    val itemIndexConverterRef by rememberUpdatedState(itemIndexConverter)
+
+    DisposableEffect(items, configurations, backwardPreloadCount, forwardPreloadCount, firstIndex, lastIndex) {
+        items.setActiveItems {
+            buildMap {
+                if ((firstIndex >= 0) && (lastIndex >= 0)) {
+                    for (i in firstIndex - backwardPreloadCount.coerceAtLeast(0)..lastIndex + forwardPreloadCount.coerceAtLeast(0)) {
+                        put(
+                            configurations.getOrNull(itemIndexConverterRef(i)) ?: continue,
+                            if (i in firstIndex..lastIndex) ActiveLifecycleState.RESUMED else ActiveLifecycleState.CREATED,
+                        )
+                    }
+                }
+            }
+        }
+
+        onDispose {}
+    }
+}
diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/GalleryContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/GalleryContent.kt
index 16e5657d..5eb59f0a 100644
--- a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/GalleryContent.kt
+++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/GalleryContent.kt
@@ -6,30 +6,34 @@ import androidx.compose.animation.ExperimentalSharedTransitionApi
 import androidx.compose.animation.SharedTransitionLayout
 import androidx.compose.animation.SharedTransitionScope
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.itemsIndexed
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.unit.dp
-import com.arkivanov.sample.shared.painterResource
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.extensions.compose.lazyitems.ChildItemsLifecycleController
+import com.arkivanov.decompose.extensions.compose.subscribeAsState
+import com.arkivanov.sample.shared.sharedtransitions.thumbnail.ThumbnailContent
 import com.arkivanov.sample.shared.utils.TopAppBar
 import com.arkivanov.sample.shared.utils.WebDocumentTitle
 
-@OptIn(ExperimentalSharedTransitionApi::class)
+@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalDecomposeApi::class)
 @Composable
 internal fun SharedTransitionScope.GalleryContent(
     component: GalleryComponent,
     animatedVisibilityScope: AnimatedVisibilityScope,
     modifier: Modifier = Modifier,
 ) {
+    val childItems by component.items.subscribeAsState()
+    val lazyGridState = rememberLazyGridState()
+
     WebDocumentTitle(title = "Shared Transitions Gallery")
 
     Column(modifier = modifier) {
@@ -38,23 +42,24 @@ internal fun SharedTransitionScope.GalleryContent(
         LazyVerticalGrid(
             columns = GridCells.Adaptive(minSize = 128.dp),
             modifier = Modifier.fillMaxWidth().weight(1F),
+            state = lazyGridState,
         ) {
-            itemsIndexed(items = component.images) { index, image ->
-                Image(
-                    painter = painterResource(image.resourceId),
-                    contentDescription = null,
-                    modifier = Modifier
-                        .aspectRatio(1F)
-                        .sharedBounds(
-                            sharedContentState = rememberSharedContentState(key = image.id),
-                            animatedVisibilityScope = animatedVisibilityScope,
-                        )
-                        .clickable { component.onImageClicked(index = index) },
-                    contentScale = ContentScale.Crop,
+            items(items = childItems.items, key = { it.id }) { image ->
+                ThumbnailContent(
+                    component = component.items[image],
+                    animatedVisibilityScope = animatedVisibilityScope,
                 )
             }
         }
     }
+
+    ChildItemsLifecycleController(
+        items = component.items,
+        lazyGridState = lazyGridState,
+        forwardPreloadCount = lazyGridState.layoutInfo.maxSpan,
+        backwardPreloadCount = lazyGridState.layoutInfo.maxSpan,
+        itemIndexConverter = { it },
+    )
 }
 
 @OptIn(ExperimentalSharedTransitionApi::class)
diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/ThumbnailContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/ThumbnailContent.kt
new file mode 100644
index 00000000..ab77c8b1
--- /dev/null
+++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/ThumbnailContent.kt
@@ -0,0 +1,33 @@
+package com.arkivanov.sample.shared.sharedtransitions.thumbnail
+
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import com.arkivanov.sample.shared.painterResource
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+fun SharedTransitionScope.ThumbnailContent(
+    component: ThumbnailComponent,
+    animatedVisibilityScope: AnimatedVisibilityScope,
+    modifier: Modifier = Modifier,
+) {
+    Image(
+        painter = painterResource(component.image.resourceId),
+        contentDescription = null,
+        modifier = modifier
+            .aspectRatio(1F)
+            .sharedBounds(
+                sharedContentState = rememberSharedContentState(key = component.image.id),
+                animatedVisibilityScope = animatedVisibilityScope,
+            )
+            .clickable(onClick = component::onClicked),
+        contentScale = ContentScale.Crop,
+    )
+}
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/SimpleLazyChildItems.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/SimpleLazyChildItems.kt
new file mode 100644
index 00000000..7b33d40e
--- /dev/null
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/SimpleLazyChildItems.kt
@@ -0,0 +1,32 @@
+package com.arkivanov.sample.shared
+
+import com.arkivanov.decompose.Cancellation
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.router.items.ChildItems
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+import com.arkivanov.decompose.router.items.ItemsNavigation
+import com.arkivanov.decompose.router.items.ItemsNavigator
+import com.arkivanov.decompose.router.items.LazyChildItems
+import com.arkivanov.decompose.value.MutableValue
+
+@OptIn(ExperimentalDecomposeApi::class)
+class SimpleLazyChildItems<C : Any, T : Any>(
+    private val items: Map<C, T>,
+) : LazyChildItems<C, T>(), ItemsNavigator<C> by ItemsNavigation() {
+
+    private val _value =
+        MutableValue(
+            ChildItems(
+                items = items.keys.toList(),
+                activeItems = items.mapValues { (_, instance) -> instance to ActiveLifecycleState.CREATED },
+            )
+        )
+
+    override val value: ChildItems<C, T> by _value::value
+
+    override fun subscribe(observer: (ChildItems<C, T>) -> Unit): Cancellation =
+        _value.subscribe(observer)
+
+    override fun get(configuration: C): T =
+        items.getValue(configuration)
+}
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/DefaultSharedTransitionsComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/DefaultSharedTransitionsComponent.kt
index e2fd666e..39bb96ac 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/DefaultSharedTransitionsComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/DefaultSharedTransitionsComponent.kt
@@ -20,7 +20,6 @@ import com.arkivanov.sample.shared.sharedtransitions.SharedTransitionsComponent.
 import com.arkivanov.sample.shared.sharedtransitions.SharedTransitionsComponent.Child.PhotoChild
 import com.arkivanov.sample.shared.sharedtransitions.gallery.DefaultGalleryComponent
 import com.arkivanov.sample.shared.sharedtransitions.photo.DefaultPhotoComponent
-import com.arkivanov.sample.shared.sharedtransitions.photo.Image
 import kotlinx.serialization.Serializable
 
 class DefaultSharedTransitionsComponent(
@@ -45,7 +44,7 @@ class DefaultSharedTransitionsComponent(
             serializer = Config.serializer(),
             initialStack = { getInitialStack(deepLinkUrl) },
             handleBackButton = true,
-            childFactory = { config, _ -> child(config) },
+            childFactory = ::child,
         )
 
     override val stack: Value<ChildStack<*, Child>> = _stack
@@ -66,11 +65,12 @@ class DefaultSharedTransitionsComponent(
             onBeforeNavigate = { false },
         )
 
-    private fun child(config: Config): Child =
+    private fun child(config: Config, ctx: ComponentContext): Child =
         when (config) {
             is Config.Gallery ->
                 GalleryChild(
                     DefaultGalleryComponent(
+                        componentContext = ctx,
                         images = images,
                         onImageSelected = { nav.pushNew(Config.Photo(id = it)) },
                         onFinished = onFinished,
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/Image.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/Image.kt
similarity index 67%
rename from sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/Image.kt
rename to sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/Image.kt
index 4e4ab1e0..57c4c7b6 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/Image.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/Image.kt
@@ -1,4 +1,4 @@
-package com.arkivanov.sample.shared.sharedtransitions.photo
+package com.arkivanov.sample.shared.sharedtransitions
 
 import com.arkivanov.sample.shared.ImageResourceId
 
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/DefaultGalleryComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/DefaultGalleryComponent.kt
index e1fee6be..2b311f52 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/DefaultGalleryComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/DefaultGalleryComponent.kt
@@ -1,16 +1,40 @@
 package com.arkivanov.sample.shared.sharedtransitions.gallery
 
-import com.arkivanov.sample.shared.sharedtransitions.photo.Image
+import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.router.children.transientNavStateSaver
+import com.arkivanov.decompose.router.items.Items
+import com.arkivanov.decompose.router.items.ItemsNavigation
+import com.arkivanov.decompose.router.items.LazyChildItems
+import com.arkivanov.decompose.router.items.childItems
+import com.arkivanov.sample.shared.sharedtransitions.Image
+import com.arkivanov.sample.shared.sharedtransitions.thumbnail.DefaultThumbnailComponent
+import com.arkivanov.sample.shared.sharedtransitions.thumbnail.ThumbnailComponent
 
+@OptIn(ExperimentalDecomposeApi::class)
 class DefaultGalleryComponent(
-    override val images: List<Image>,
+    componentContext: ComponentContext,
+    private val images: List<Image>,
     private val onImageSelected: (id: Int) -> Unit,
     private val onFinished: () -> Unit,
-) : GalleryComponent {
+) : GalleryComponent, ComponentContext by componentContext {
 
-    override fun onImageClicked(id: Int) {
-        onImageSelected(id)
-    }
+    private val nav = ItemsNavigation<Image>()
+
+    override val items: LazyChildItems<Image, ThumbnailComponent> =
+        childItems(
+            source = nav,
+            stateSaver = transientNavStateSaver(),
+            initialItems = { Items(items = images) },
+            childFactory = ::child,
+        )
+
+    private fun child(image: Image, ctx: ComponentContext): ThumbnailComponent =
+        DefaultThumbnailComponent(
+            componentContext = ctx,
+            image = image,
+            onSelected = { onImageSelected(image.id) },
+        )
 
     override fun onCloseClicked() {
         onFinished()
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/GalleryComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/GalleryComponent.kt
index a422746f..b3fb91b6 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/GalleryComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/GalleryComponent.kt
@@ -1,11 +1,14 @@
 package com.arkivanov.sample.shared.sharedtransitions.gallery
 
-import com.arkivanov.sample.shared.sharedtransitions.photo.Image
+import com.arkivanov.decompose.ExperimentalDecomposeApi
+import com.arkivanov.decompose.router.items.LazyChildItems
+import com.arkivanov.sample.shared.sharedtransitions.Image
+import com.arkivanov.sample.shared.sharedtransitions.thumbnail.ThumbnailComponent
 
 interface GalleryComponent {
 
-    val images: List<Image>
+    @OptIn(ExperimentalDecomposeApi::class)
+    val items: LazyChildItems<Image, ThumbnailComponent>
 
-    fun onImageClicked(index: Int)
     fun onCloseClicked()
 }
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/PreviewGalleryComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/PreviewGalleryComponent.kt
index fe9e7aeb..5df1a1ed 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/PreviewGalleryComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/PreviewGalleryComponent.kt
@@ -1,18 +1,25 @@
 package com.arkivanov.sample.shared.sharedtransitions.gallery
 
+import com.arkivanov.decompose.router.items.LazyChildItems
 import com.arkivanov.sample.shared.ImageResourceId
-import com.arkivanov.sample.shared.sharedtransitions.photo.Image
+import com.arkivanov.sample.shared.SimpleLazyChildItems
+import com.arkivanov.sample.shared.sharedtransitions.Image
+import com.arkivanov.sample.shared.sharedtransitions.thumbnail.PreviewThumbnailComponent
+import com.arkivanov.sample.shared.sharedtransitions.thumbnail.ThumbnailComponent
 
 class PreviewGalleryComponent : GalleryComponent {
 
-    override val images: List<Image> =
-        List(10) { index ->
-            Image(
-                id = index,
-                resourceId = ImageResourceId.entries[index % ImageResourceId.entries.size],
-            )
-        }
+    override val items: LazyChildItems<Image, ThumbnailComponent> =
+        SimpleLazyChildItems(
+            List(10) { index ->
+                PreviewThumbnailComponent(
+                    image = Image(
+                        id = index,
+                        resourceId = ImageResourceId.entries[index % ImageResourceId.entries.size],
+                    ),
+                )
+            }.associateBy { it.image },
+        )
 
-    override fun onImageClicked(index: Int) {}
     override fun onCloseClicked() {}
 }
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/DefaultPhotoComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/DefaultPhotoComponent.kt
index 0d54c227..009a14fb 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/DefaultPhotoComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/DefaultPhotoComponent.kt
@@ -1,5 +1,7 @@
 package com.arkivanov.sample.shared.sharedtransitions.photo
 
+import com.arkivanov.sample.shared.sharedtransitions.Image
+
 class DefaultPhotoComponent(
     override val image: Image,
     private val onFinished: () -> Unit,
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/PhotoComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/PhotoComponent.kt
index cc381017..72de4f4d 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/PhotoComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/PhotoComponent.kt
@@ -1,5 +1,7 @@
 package com.arkivanov.sample.shared.sharedtransitions.photo
 
+import com.arkivanov.sample.shared.sharedtransitions.Image
+
 interface PhotoComponent {
 
     val image: Image
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/PreviewPhotoComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/PreviewPhotoComponent.kt
index 51797d34..6deee3e3 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/PreviewPhotoComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/photo/PreviewPhotoComponent.kt
@@ -1,6 +1,7 @@
 package com.arkivanov.sample.shared.sharedtransitions.photo
 
 import com.arkivanov.sample.shared.ImageResourceId
+import com.arkivanov.sample.shared.sharedtransitions.Image
 
 class PreviewPhotoComponent : PhotoComponent {
 
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/DefaultThumbnailComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/DefaultThumbnailComponent.kt
new file mode 100644
index 00000000..97152179
--- /dev/null
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/DefaultThumbnailComponent.kt
@@ -0,0 +1,15 @@
+package com.arkivanov.sample.shared.sharedtransitions.thumbnail
+
+import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.sample.shared.sharedtransitions.Image
+
+class DefaultThumbnailComponent(
+    componentContext: ComponentContext,
+    override val image: Image,
+    private val onSelected: () -> Unit,
+) : ThumbnailComponent, ComponentContext by componentContext {
+
+    override fun onClicked() {
+        onSelected()
+    }
+}
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/PreviewThumbnailComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/PreviewThumbnailComponent.kt
new file mode 100644
index 00000000..f309ecf8
--- /dev/null
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/PreviewThumbnailComponent.kt
@@ -0,0 +1,11 @@
+package com.arkivanov.sample.shared.sharedtransitions.thumbnail
+
+import com.arkivanov.sample.shared.ImageResourceId
+import com.arkivanov.sample.shared.sharedtransitions.Image
+
+class PreviewThumbnailComponent(
+    override val image: Image = Image(id = 1, resourceId = ImageResourceId.CAT_1),
+) : ThumbnailComponent {
+
+    override fun onClicked() {}
+}
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/ThumbnailComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/ThumbnailComponent.kt
new file mode 100644
index 00000000..60f0a519
--- /dev/null
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/thumbnail/ThumbnailComponent.kt
@@ -0,0 +1,10 @@
+package com.arkivanov.sample.shared.sharedtransitions.thumbnail
+
+import com.arkivanov.sample.shared.sharedtransitions.Image
+
+interface ThumbnailComponent {
+
+    val image: Image
+
+    fun onClicked()
+}


Test patch:
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestBackCallback.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestBackCallback.kt
new file mode 100644
index 00000000..f5859ddf
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestBackCallback.kt
@@ -0,0 +1,28 @@
+package com.arkivanov.decompose
+
+import com.arkivanov.essenty.backhandler.BackCallback
+import kotlin.test.assertEquals
+
+class TestBackCallback(
+    isEnabled: Boolean = true,
+    priority: Int = PRIORITY_DEFAULT,
+) : BackCallback(isEnabled = isEnabled, priority = priority) {
+
+    val events: MutableList<Event> = ArrayList()
+
+    override fun onBack() {
+        events += Event.OnBack
+    }
+
+    sealed interface Event {
+        data object OnBack : Event
+    }
+}
+
+fun TestBackCallback.assertNoEvents() {
+    assertEvents()
+}
+
+fun TestBackCallback.assertEvents(vararg events: TestBackCallback.Event) {
+    assertEquals(events.asList(), this.events)
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestComponentContext.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestComponentContext.kt
new file mode 100644
index 00000000..a4766723
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestComponentContext.kt
@@ -0,0 +1,39 @@
+package com.arkivanov.decompose
+
+import com.arkivanov.essenty.backhandler.BackDispatcher
+import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
+import com.arkivanov.essenty.lifecycle.Lifecycle
+import com.arkivanov.essenty.lifecycle.LifecycleRegistry
+import com.arkivanov.essenty.lifecycle.create
+import com.arkivanov.essenty.lifecycle.destroy
+import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
+
+internal class TestComponentContext(
+    override val lifecycle: LifecycleRegistry = LifecycleRegistry(Lifecycle.State.RESUMED),
+    override val stateKeeper: StateKeeperDispatcher = StateKeeperDispatcher(),
+    override val instanceKeeper: InstanceKeeperDispatcher = InstanceKeeperDispatcher(),
+    override val backHandler: BackDispatcher = BackDispatcher(),
+) : ComponentContext {
+
+    override val componentContextFactory: ComponentContextFactory<ComponentContext> =
+        ComponentContextFactory(::DefaultComponentContext)
+}
+
+internal fun TestComponentContext.recreate(isConfigurationChange: Boolean = false): TestComponentContext {
+    val oldLifecycleState = lifecycle.state
+
+    if (oldLifecycleState == Lifecycle.State.INITIALIZED) {
+        lifecycle.create()
+    }
+
+    val newStateKeeper = stateKeeper.recreate(isConfigurationChange = isConfigurationChange)
+    val newInstanceKeeper = if (isConfigurationChange) instanceKeeper else InstanceKeeperDispatcher()
+
+    lifecycle.destroy()
+
+    return TestComponentContext(
+        lifecycle = LifecycleRegistry(oldLifecycleState),
+        stateKeeper = newStateKeeper,
+        instanceKeeper = newInstanceKeeper,
+    )
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestUtils.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestUtils.kt
index be50466c..1eb837a0 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestUtils.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/TestUtils.kt
@@ -1,6 +1,7 @@
 package com.arkivanov.decompose
 
 import com.arkivanov.essenty.statekeeper.StateKeeper
+import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.serializer
 
@@ -21,3 +22,8 @@ internal inline fun <reified T> T.serializeAndDeserialize(): T {
 
     return json.decodeFromString(serializer, json.encodeToString(serializer, this))
 }
+
+fun StateKeeperDispatcher.recreate(isConfigurationChange: Boolean = false): StateKeeperDispatcher =
+    StateKeeperDispatcher(
+        savedState = save().let { if (isConfigurationChange) it else it.serializeAndDeserialize() },
+    )
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/lifecycle/MergedLifecycleTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/lifecycle/MergedLifecycleTest.kt
index 3b31abef..34883093 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/lifecycle/MergedLifecycleTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/lifecycle/MergedLifecycleTest.kt
@@ -1,6 +1,6 @@
 package com.arkivanov.decompose.lifecycle
 
-import com.arkivanov.decompose.lifecycle.MergedLifecycleTest.TestLifecycleCallbacks.Event
+import com.arkivanov.decompose.lifecycle.TestLifecycleCallbacks.Event
 import com.arkivanov.essenty.lifecycle.Lifecycle
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
 import com.arkivanov.essenty.lifecycle.create
@@ -506,48 +506,4 @@ class MergedLifecycleTest {
             assertEquals(emptySet(), callbacks)
         }
     }
-
-    private class TestLifecycleCallbacks : Lifecycle.Callbacks {
-        private val events = ArrayList<Event>()
-
-        override fun onCreate() {
-            events += Event.ON_CREATE
-        }
-
-        override fun onStart() {
-            events += Event.ON_START
-        }
-
-        override fun onResume() {
-            events += Event.ON_RESUME
-        }
-
-        override fun onPause() {
-            events += Event.ON_PAUSE
-        }
-
-        override fun onStop() {
-            events += Event.ON_STOP
-        }
-
-        override fun onDestroy() {
-            events += Event.ON_DESTROY
-        }
-
-        fun assertEvents(vararg events: Event) {
-            assertEquals(events.toList(), this.events)
-        }
-
-        fun assertNoEvents() {
-            assertEquals(emptyList(), events)
-        }
-
-        fun clear() {
-            events.clear()
-        }
-
-        enum class Event {
-            ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY
-        }
-    }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/lifecycle/TestLifecycleCallbacks.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/lifecycle/TestLifecycleCallbacks.kt
new file mode 100644
index 00000000..d572241b
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/lifecycle/TestLifecycleCallbacks.kt
@@ -0,0 +1,56 @@
+package com.arkivanov.decompose.lifecycle
+
+import com.arkivanov.essenty.lifecycle.Lifecycle
+import kotlin.test.assertEquals
+
+class TestLifecycleCallbacks(
+    private val onEvent: (Event) -> Unit = {},
+) : Lifecycle.Callbacks {
+
+    val events: MutableList<Event> = ArrayList<Event>()
+
+    override fun onCreate() {
+        addEvent(Event.ON_CREATE)
+    }
+
+    override fun onStart() {
+        addEvent(Event.ON_START)
+    }
+
+    override fun onResume() {
+        addEvent(Event.ON_RESUME)
+    }
+
+    override fun onPause() {
+        addEvent(Event.ON_PAUSE)
+    }
+
+    override fun onStop() {
+        addEvent(Event.ON_STOP)
+    }
+
+    override fun onDestroy() {
+        addEvent(Event.ON_DESTROY)
+    }
+
+    private fun addEvent(event: Event) {
+        events += event
+        onEvent(event)
+    }
+
+    fun assertEvents(vararg events: Event) {
+        assertEquals(events.asList(), this.events)
+    }
+
+    fun assertNoEvents() {
+        assertEquals(emptyList(), events)
+    }
+
+    fun clear() {
+        events.clear()
+    }
+
+    enum class Event {
+        ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildControllerTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildControllerTest.kt
new file mode 100644
index 00000000..47a2b142
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildControllerTest.kt
@@ -0,0 +1,386 @@
+package com.arkivanov.decompose.router.children
+
+import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.TestBackCallback
+import com.arkivanov.decompose.TestBackCallback.Event
+import com.arkivanov.decompose.TestComponentContext
+import com.arkivanov.decompose.assertEvents
+import com.arkivanov.decompose.assertNoEvents
+import com.arkivanov.decompose.lifecycle.TestLifecycleCallbacks
+import com.arkivanov.decompose.recreate
+import com.arkivanov.decompose.router.Component
+import com.arkivanov.decompose.router.TestInstance
+import com.arkivanov.decompose.serializeAndDeserialize
+import com.arkivanov.essenty.instancekeeper.getOrCreate
+import com.arkivanov.essenty.lifecycle.Lifecycle
+import kotlinx.serialization.builtins.serializer
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNotSame
+import kotlin.test.assertNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+@Suppress("TestFunctionName")
+class ChildControllerTest {
+
+    @Test
+    fun WHEN_create_THEN_child_created() {
+        val controller = controller().apply { init() }
+
+        controller.create(configuration = 1)
+
+        controller.assertChildCreated(config = 1)
+    }
+
+    @Test
+    fun WHEN_create_THEN_returns_created_child() {
+        val controller = controller().apply { init() }
+
+        val instance = controller.create(configuration = 1)
+
+        assertEquals(Lifecycle.State.CREATED, instance.lifecycle.state)
+    }
+
+    @Test
+    fun WHEN_start_THEN_child_started() {
+        val controller = controller().apply { init() }
+
+        controller.start(configuration = 1)
+
+        controller.assertChildStarted(config = 1)
+    }
+
+    @Test
+    fun WHEN_start_THEN_returns_started_child() {
+        val controller = controller().apply { init() }
+
+        val instance = controller.start(configuration = 1)
+
+        assertEquals(Lifecycle.State.STARTED, instance.lifecycle.state)
+    }
+
+    @Test
+    fun WHEN_resume_THEN_child_resumed() {
+        val controller = controller().apply { init() }
+
+        controller.resume(configuration = 1)
+
+        controller.assertChildResumed(config = 1)
+    }
+
+    @Test
+    fun WHEN_resume_THEN_returns_resumed_child() {
+        val controller = controller().apply { init() }
+
+        val instance = controller.resume(configuration = 1)
+
+        assertEquals(Lifecycle.State.RESUMED, instance.lifecycle.state)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_WHEN_start_THEN_child_started() {
+        val controller = controller().apply { init() }
+        controller.resume(configuration = 1)
+
+        controller.start(configuration = 1)
+
+        controller.assertChildStarted(config = 1)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_WHEN_start_THEN_returns_started_child() {
+        val controller = controller().apply { init() }
+        controller.resume(configuration = 1)
+
+        val instance = controller.start(configuration = 1)
+
+        assertEquals(Lifecycle.State.STARTED, instance.lifecycle.state)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_WHEN_create_THEN_child_created() {
+        val controller = controller().apply { init() }
+        controller.resume(configuration = 1)
+
+        controller.create(configuration = 1)
+
+        controller.assertChildCreated(config = 1)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_WHEN_create_THEN_returns_created_child() {
+        val controller = controller().apply { init() }
+        controller.resume(configuration = 1)
+
+        val instance = controller.create(configuration = 1)
+
+        assertEquals(Lifecycle.State.CREATED, instance.lifecycle.state)
+    }
+
+    @Test
+    fun GIVEN_child_created_WHEN_destroy_THEN_child_destroyed() {
+        val map = HashMap<Int, Component<Int>>()
+        val controller = controller(map = map).apply { init() }
+        controller.create(configuration = 1)
+
+        controller.destroy(configuration = 1)
+
+        controller.assertChildDestroyed(config = 1)
+        assertEquals(Lifecycle.State.DESTROYED, map[1]?.lifecycle?.state)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_WHEN_destroy_THEN_child_destroyed() {
+        val map = HashMap<Int, Component<Int>>()
+        val controller = controller(map = map).apply { init() }
+        controller.resume(configuration = 1)
+
+        controller.destroy(configuration = 1)
+
+        controller.assertChildDestroyed(config = 1)
+        assertEquals(Lifecycle.State.DESTROYED, map[1]?.lifecycle?.state)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_WHEN_destroy_THEN_destroy_events_called_in_order() {
+        val controller = controller().apply { init() }
+        controller.resume(configuration = 1)
+
+        val events = ArrayList<Any>()
+        controller.requireChild(1).lifecycle.subscribe(TestLifecycleCallbacks { events += it })
+        controller.requireChild(1).instanceKeeper.getOrCreate { TestInstance { events += "instance_destroyed" } }
+        events.clear()
+
+        controller.destroy(configuration = 1)
+
+        assertEquals(
+            listOf<Any>(
+                TestLifecycleCallbacks.Event.ON_PAUSE,
+                TestLifecycleCallbacks.Event.ON_STOP,
+                TestLifecycleCallbacks.Event.ON_DESTROY,
+                "instance_destroyed",
+            ),
+            events,
+        )
+    }
+
+    @Test
+    fun GIVEN_child_created_WHEN_recreated_THEN_child_state_restored() {
+        var ctx = TestComponentContext()
+        var controller = controller(ctx = ctx).apply { init() }
+        controller.create(configuration = 1)
+        controller.requireChild(1).stateKeeper.register(key = "key", strategy = Int.serializer()) { 100 }
+
+        val savedState = controller.saveState(configuration = 1)?.serializeAndDeserialize()
+
+        ctx = ctx.recreate()
+        controller = controller(ctx = ctx)
+
+        controller.init {
+            controller.create(configuration = 1, savedState = savedState)
+        }
+
+        val restoredState = controller.requireChild(1).stateKeeper.consume(key = "key", strategy = Int.serializer())
+
+        assertEquals(100, restoredState)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_WHEN_recreated_THEN_child_state_restored() {
+        var ctx = TestComponentContext()
+        var controller = controller(ctx = ctx).apply { init() }
+        controller.resume(configuration = 1)
+        controller.requireChild(1).stateKeeper.register(key = "key", strategy = Int.serializer()) { 100 }
+
+        val savedState = controller.saveState(configuration = 1)?.serializeAndDeserialize()
+
+        ctx = ctx.recreate()
+        controller = controller(ctx = ctx)
+
+        controller.init {
+            controller.create(configuration = 1, savedState = savedState)
+        }
+
+        val restoredState = controller.requireChild(1).stateKeeper.consume(key = "key", strategy = Int.serializer())
+
+        assertEquals(100, restoredState)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_and_retained_instance_created_WHEN_destroy_THEN_instance_destroyed() {
+        val ctx = TestComponentContext()
+        val controller = controller(ctx = ctx).apply { init() }
+        controller.resume(configuration = 1)
+        val instance = controller.requireChild(1).instanceKeeper.getOrCreate(key = "key") { TestInstance() }
+
+        controller.destroy(configuration = 1)
+
+        assertTrue(instance.isDestroyed)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_and_retained_instance_created_WHEN_configuration_changed_THEN_same_instance() {
+        var ctx = TestComponentContext()
+        var controller = controller(ctx = ctx).apply { init() }
+        controller.resume(configuration = 1)
+        val instance1 = controller.requireChild(1).instanceKeeper.getOrCreate(key = "key") { TestInstance() }
+
+        ctx = ctx.recreate(isConfigurationChange = true)
+        controller = controller(ctx = ctx)
+
+        controller.init {
+            controller.create(configuration = 1)
+        }
+
+        val instance2 = controller.requireChild(1).instanceKeeper.getOrCreate(key = "key") { TestInstance() }
+
+        assertSame(instance1, instance2)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_and_retained_instance_created_WHEN_configuration_changed_and_drop_state_THEN_instance_destroyed() {
+        var ctx = TestComponentContext()
+        var controller = controller(ctx = ctx).apply { init() }
+        controller.resume(configuration = 1)
+        val instance = controller.requireChild(1).instanceKeeper.getOrCreate(key = "key") { TestInstance() }
+
+        ctx = ctx.recreate(isConfigurationChange = true)
+        controller = controller(ctx = ctx)
+
+        controller.init(dropState = true) {
+            controller.create(configuration = 1)
+        }
+
+        assertTrue(instance.isDestroyed)
+    }
+
+    @Test
+    fun GIVEN_child_resumed_and_retained_instance_created_WHEN_configuration_changed_and_drop_state_THEN_new_instance() {
+        var ctx = TestComponentContext()
+        var controller = controller(ctx = ctx).apply { init() }
+        controller.resume(configuration = 1)
+        val instance1 = controller.requireChild(1).instanceKeeper.getOrCreate(key = "key") { TestInstance() }
+
+        ctx = ctx.recreate(isConfigurationChange = true)
+        controller = controller(ctx = ctx)
+
+        controller.init(dropState = true) {
+            controller.create(configuration = 1)
+        }
+
+        val instance2 = controller.requireChild(1).instanceKeeper.getOrCreate(key = "key") { TestInstance() }
+
+        assertNotSame(instance1, instance2)
+    }
+
+    @Test
+    fun GIVEN_child_created_WHEN_back_callback_registered_THEN_back_handler_disabled() {
+        val ctx = TestComponentContext()
+        val controller = controller(ctx = ctx).apply { init() }
+        controller.create(configuration = 1)
+
+        controller.requireChild(1).backHandler.register(TestBackCallback())
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_child_created_and_back_callback_registered_WHEN_back_THEN_callback_not_called() {
+        val ctx = TestComponentContext()
+        val controller = controller(ctx = ctx).apply { init() }
+        controller.create(configuration = 1)
+        val callback = TestBackCallback()
+        controller.requireChild(1).backHandler.register(callback)
+
+        ctx.backHandler.back()
+
+        callback.assertNoEvents()
+    }
+
+    @Test
+    fun GIVEN_child_started_and_back_callback_registered_WHEN_back_THEN_callback_called() {
+        val ctx = TestComponentContext()
+        val controller = controller(ctx = ctx).apply { init() }
+        controller.start(configuration = 1)
+        val callback = TestBackCallback()
+        controller.requireChild(1).backHandler.register(callback)
+
+        ctx.backHandler.back()
+
+        callback.assertEvents(Event.OnBack)
+    }
+
+    @Test
+    fun GIVEN_child_started_and_back_callback_registered_WHEN_create_THEN_back_handler_disabled() {
+        val ctx = TestComponentContext()
+        val controller = controller(ctx = ctx).apply { init() }
+        controller.start(configuration = 1)
+        val callback = TestBackCallback()
+        controller.requireChild(1).backHandler.register(callback)
+
+        controller.create(configuration = 1)
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_child_started_and_back_callback_registered_and_child_created_WHEN_back_THEN_callback_not_called() {
+        val ctx = TestComponentContext()
+        val controller = controller(ctx = ctx).apply { init() }
+        controller.start(configuration = 1)
+        val callback = TestBackCallback()
+        controller.requireChild(1).backHandler.register(callback)
+        controller.create(configuration = 1)
+
+        ctx.backHandler.back()
+
+        callback.assertNoEvents()
+    }
+
+    private fun controller(
+        ctx: ComponentContext = TestComponentContext(),
+        map: MutableMap<Int, Component<Int>> = HashMap(),
+        childFactory: (Int, ComponentContext) -> Component<Int> = { cfg, ctx ->
+            Component(cfg, ctx).also {
+                map[cfg] = it
+            }
+        },
+    ): ChildController<Int, Component<Int>, ComponentContext> =
+        ChildController(
+            componentContext = ctx,
+            key = "key",
+            childFactory = childFactory,
+        )
+
+    private fun ChildController<Int, Component<Int>, *>.assertChildCreated(config: Int) {
+        assertChildCreated(config = config, lifecycleState = Lifecycle.State.CREATED)
+    }
+
+    private fun ChildController<Int, Component<Int>, *>.assertChildStarted(config: Int) {
+        assertChildCreated(config = config, lifecycleState = Lifecycle.State.STARTED)
+    }
+
+    private fun ChildController<Int, Component<Int>, *>.assertChildResumed(config: Int) {
+        assertChildCreated(config = config, lifecycleState = Lifecycle.State.RESUMED)
+    }
+
+    private fun ChildController<Int, Component<Int>, *>.assertChildCreated(
+        config: Int,
+        lifecycleState: Lifecycle.State,
+    ) {
+        val component = assertNotNull(get(config))
+        assertEquals(config, component.config)
+        assertEquals(lifecycleState, component.lifecycle.state)
+        assertEquals(lifecycleState, getLifecycleState(config))
+    }
+
+    private fun ChildController<Int, Component<Int>, *>.requireChild(config: Int): Component<Int> =
+        assertNotNull(get(config))
+
+    private fun ChildController<Int, Component<Int>, *>.assertChildDestroyed(config: Int) {
+        assertNull(get(config))
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt
index 19229abd..7a399630 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt
@@ -1,5 +1,9 @@
 package com.arkivanov.decompose.router.children
 
+import com.arkivanov.decompose.TestBackCallback
+import com.arkivanov.decompose.TestBackCallback.Event.OnBack
+import com.arkivanov.decompose.assertEvents
+import com.arkivanov.decompose.assertNoEvents
 import com.arkivanov.decompose.router.children.ChildNavState.Status.CREATED
 import com.arkivanov.decompose.router.children.ChildNavState.Status.DESTROYED
 import com.arkivanov.decompose.router.children.ChildNavState.Status.RESUMED
@@ -385,4 +389,56 @@ class ChildrenBackPressedTest : ChildrenTestBase() {
 
         children.assertChildren(1 to 1, 3 to 3)
     }
+
+    @Test
+    fun GIVEN_callback_registered_in_context_and_in_child_WHEN_back_THEN_child_callback_called() {
+        val children by context.children(initialState = stateOf(1 by RESUMED))
+
+        backDispatcher.register(TestBackCallback())
+        val callback = TestBackCallback()
+        children.getByConfig(1).requireInstance().backHandler.register(callback)
+
+        backDispatcher.back()
+
+        callback.assertEvents(OnBack)
+    }
+
+    @Test
+    fun GIVEN_callback_registered_in_context_and_in_child_WHEN_back_THEN_context_callback_not_called() {
+        val children by context.children(initialState = stateOf(1 by RESUMED))
+
+        val callback = TestBackCallback()
+        backDispatcher.register(callback)
+        children.getByConfig(1).requireInstance().backHandler.register(TestBackCallback())
+
+        backDispatcher.back()
+
+        callback.assertNoEvents()
+    }
+
+    @Test
+    fun GIVEN_callback_registered_in_child_and_in_context_WHEN_back_THEN_child_callback_called() {
+        val children by context.children(initialState = stateOf(1 by RESUMED))
+
+        val callback = TestBackCallback()
+        children.getByConfig(1).requireInstance().backHandler.register(callback)
+        backDispatcher.register(TestBackCallback())
+
+        backDispatcher.back()
+
+        callback.assertEvents(OnBack)
+    }
+
+    @Test
+    fun GIVEN_callback_registered_in_child_and_in_context_WHEN_back_THEN_context_callback_not_called() {
+        val children by context.children(initialState = stateOf(1 by RESUMED))
+
+        children.getByConfig(1).requireInstance().backHandler.register(TestBackCallback())
+        val callback = TestBackCallback()
+        backDispatcher.register(callback)
+
+        backDispatcher.back()
+
+        callback.assertNoEvents()
+    }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/TransientNavStateSaverTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/TransientNavStateSaverTest.kt
new file mode 100644
index 00000000..66acb54e
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/TransientNavStateSaverTest.kt
@@ -0,0 +1,28 @@
+package com.arkivanov.decompose.router.children
+
+import com.arkivanov.decompose.serializeAndDeserialize
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+@Suppress("TestFunctionName")
+class TransientNavStateSaverTest {
+
+    @Test
+    fun WHEN_not_persistent_THEN_restores_saved_state() {
+        val saver = transientNavStateSaver<Int>()
+
+        val savedState = saver.saveState(state = 1)?.let(saver::restoreState)
+
+        assertEquals(1, savedState)
+    }
+
+    @Test
+    fun WHEN_persistent_THEN_does_not_restore_saved_state() {
+        val saver = transientNavStateSaver<Int>()
+
+        val savedState = saver.saveState(state = 1)?.serializeAndDeserialize()?.let(saver::restoreState)
+
+        assertNull(savedState)
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyComponentState.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyComponentState.kt
new file mode 100644
index 00000000..b10201d6
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyComponentState.kt
@@ -0,0 +1,10 @@
+package com.arkivanov.decompose.router.items
+
+enum class LazyComponentState {
+    PENDING,
+    REMOVED,
+    DESTROYED,
+    CREATED,
+    STARTED,
+    RESUMED,
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsBackButtonTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsBackButtonTest.kt
new file mode 100644
index 00000000..38ee45f0
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsBackButtonTest.kt
@@ -0,0 +1,426 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.TestBackCallback
+import com.arkivanov.decompose.TestBackCallback.Event.OnBack
+import com.arkivanov.decompose.TestComponentContext
+import com.arkivanov.decompose.assertEvents
+import com.arkivanov.decompose.assertNoEvents
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+import kotlin.test.Test
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@Suppress("TestFunctionName")
+class LazyItemsBackButtonTest {
+
+    @Test
+    fun WHEN_created_with_active_items_THEN_back_handler_disabled() {
+        val ctx = TestComponentContext()
+
+        ctx.childLazyItems(
+            initialItems = listOf(1, 2, 3, 4, 5),
+            activeItems = mapOf(
+                2 to ActiveLifecycleState.CREATED,
+                3 to ActiveLifecycleState.STARTED,
+                4 to ActiveLifecycleState.RESUMED,
+            ),
+        )
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_WHEN_created_item_registered_enabled_callback_THEN_back_handler_disabled() {
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.require(2).backHandler.register(TestBackCallback())
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_created_item_registered_enabled_callback_WHEN_back_THEN_callback_not_called() {
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(2).backHandler.register(backCallback)
+
+        ctx.backHandler.back()
+
+        backCallback.assertNoEvents()
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_WHEN_started_item_registered_enabled_callback_THEN_back_handler_enabled() {
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.require(3).backHandler.register(TestBackCallback())
+
+        assertTrue(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_started_item_registered_enabled_callback_WHEN_callback_disabled_THEN_back_handler_disabled() {
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val callback = TestBackCallback()
+        items.require(3).backHandler.register(callback)
+
+        callback.isEnabled = false
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_started_item_registered_enabled_callback_WHEN_back_THEN_callback_called() {
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(3).backHandler.register(backCallback)
+
+        ctx.backHandler.back()
+
+        backCallback.assertEvents(OnBack)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_WHEN_resumed_item_registered_enabled_callback_THEN_back_handler_enabled() {
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.require(4).backHandler.register(TestBackCallback())
+
+        assertTrue(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_resumed_item_registered_enabled_callback_WHEN_callback_disabled_THEN_back_handler_enabled() {
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val callback = TestBackCallback()
+        items.require(4).backHandler.register(callback)
+
+        callback.isEnabled = false
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_resumed_item_registered_enabled_callback_WHEN_back_THEN_callback_called() {
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(4).backHandler.register(backCallback)
+
+        ctx.backHandler.back()
+
+        backCallback.assertEvents(OnBack)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_started_item_registered_enabled_callback_WHEN_item_deactivated_THEN_back_handler_disabled() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(3).backHandler.register(backCallback)
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+        }
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_started_item_registered_enabled_callback_and_item_deactivated_WHEN_back_THEN_callback_not_called() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(3).backHandler.register(backCallback)
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+        }
+
+        ctx.backHandler.back()
+
+        backCallback.assertNoEvents()
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_resumed_item_registered_enabled_callback_WHEN_item_deactivated_THEN_back_handler_disabled() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(4).backHandler.register(backCallback)
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                ),
+            )
+        }
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_resumed_item_registered_enabled_callback_and_item_deactivated_WHEN_back_THEN_callback_not_called() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(4).backHandler.register(backCallback)
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                ),
+            )
+        }
+
+        ctx.backHandler.back()
+
+        backCallback.assertNoEvents()
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_started_item_registered_enabled_callback_WHEN_item_removed_THEN_back_handler_disabled() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(3).backHandler.register(backCallback)
+
+        nav.navigate { it.copy(items = listOf(1, 2, 4, 5)) }
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_started_item_registered_enabled_callback_and_item_removed_WHEN_back_THEN_callback_not_called() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(3).backHandler.register(backCallback)
+
+        nav.navigate { it.copy(items = listOf(1, 2, 4, 5)) }
+
+        ctx.backHandler.back()
+
+        backCallback.assertNoEvents()
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_resumed_item_registered_enabled_callback_WHEN_item_removed_THEN_back_handler_disabled() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(4).backHandler.register(backCallback)
+
+        nav.navigate { it.copy(items = listOf(1, 2, 3, 5)) }
+
+        assertFalse(ctx.backHandler.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_created_with_active_items_and_resumed_item_registered_enabled_callback_and_item_removed_WHEN_back_THEN_callback_not_called() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val backCallback = TestBackCallback()
+        items.require(4).backHandler.register(backCallback)
+
+        nav.navigate { it.copy(items = listOf(1, 2, 3, 5)) }
+
+        ctx.backHandler.back()
+
+        backCallback.assertNoEvents()
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsLazinessTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsLazinessTest.kt
new file mode 100644
index 00000000..e900d32e
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsLazinessTest.kt
@@ -0,0 +1,285 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.TestComponentContext
+import com.arkivanov.decompose.router.Component
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+import com.arkivanov.essenty.lifecycle.Lifecycle
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+@Suppress("TestFunctionName")
+class LazyItemsLazinessTest {
+
+    private val nav = ItemsNavigation<Int>()
+    private val ctx = TestComponentContext()
+
+    @Test
+    fun WHEN_created_with_initial_items_THEN_get_returns_components() {
+        val items = ctx.childLazyItems<Int>(initialItems = listOf(1, 2, 3))
+
+        val instances = items.value.items.map(items::get)
+
+        assertEquals(listOf(1, 2, 3), instances.map(Component<Int>::config))
+    }
+
+    @Test
+    fun WHEN_created_with_initial_items_THEN_get_returns_same_components() {
+        val items = ctx.childLazyItems<Int>(initialItems = listOf(1, 2, 3))
+
+        val instances1 = items.value.items.map(items::get)
+        val instances2 = items.value.items.map(items::get)
+
+        assertEquals(instances1, instances2)
+    }
+
+    @Test
+    fun WHEN_created_with_initial_items_THEN_get_returns_components_with_correct_lifecycles() {
+        val items = ctx.childLazyItems<Int>(initialItems = listOf(1, 2, 3))
+
+        val (child1, child2, child3) = items.value.items.map(items::get)
+
+        assertEquals(Lifecycle.State.CREATED, child1.lifecycle.state)
+        assertEquals(Lifecycle.State.CREATED, child2.lifecycle.state)
+        assertEquals(Lifecycle.State.CREATED, child3.lifecycle.state)
+    }
+
+    @Test
+    fun WHEN_created_with_initial_and_active_items_THEN_get_returns_components() {
+        val items =
+            ctx.childLazyItems<Int>(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instances = items.value.items.map(items::get)
+
+        assertEquals(listOf(1, 2, 3, 4, 5), instances.map(Component<Int>::config))
+    }
+
+    @Test
+    fun WHEN_created_with_initial_and_active_items_THEN_get_returns_same_components() {
+        val items =
+            ctx.childLazyItems<Int>(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instances1 = items.value.items.map(items::get)
+        val instances2 = items.value.items.map(items::get)
+
+        assertEquals(instances1, instances2)
+    }
+
+    @Test
+    fun WHEN_created_with_initial_and_active_items_THEN_get_returns_components_with_correct_lifecycles() {
+        val items =
+            ctx.childLazyItems<Int>(
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val (child1, child2, child3, child4, child5) = items.value.items.map(items::get)
+
+        assertEquals(Lifecycle.State.CREATED, child1.lifecycle.state)
+        assertEquals(Lifecycle.State.CREATED, child2.lifecycle.state)
+        assertEquals(Lifecycle.State.STARTED, child3.lifecycle.state)
+        assertEquals(Lifecycle.State.RESUMED, child4.lifecycle.state)
+        assertEquals(Lifecycle.State.CREATED, child5.lifecycle.state)
+    }
+
+    @Test
+    fun WHEN_active_items_changed_THEN_get_returns_components() {
+        val items =
+            ctx.childLazyItems<Int>(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.RESUMED,
+                )
+            )
+        }
+
+        val instances = items.value.items.map(items::get)
+
+        assertEquals(listOf(1, 2, 3, 4, 5), instances.map(Component<Int>::config))
+    }
+
+    @Test
+    fun WHEN_active_items_changed_THEN_get_returns_same_components() {
+        val items =
+            ctx.childLazyItems<Int>(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.RESUMED,
+                )
+            )
+        }
+
+        val instances1 = items.value.items.map(items::get)
+        val instances2 = items.value.items.map(items::get)
+
+        assertEquals(instances1, instances2)
+    }
+
+    @Test
+    fun WHEN_active_items_changed_THEN_get_returns_components_with_correct_lifecycles() {
+        val items =
+            ctx.childLazyItems<Int>(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.RESUMED,
+                )
+            )
+        }
+
+        val (child1, child2, child3, child4, child5) = items.value.items.map(items::get)
+
+        assertEquals(Lifecycle.State.STARTED, child1.lifecycle.state)
+        assertEquals(Lifecycle.State.CREATED, child2.lifecycle.state)
+        assertEquals(Lifecycle.State.CREATED, child3.lifecycle.state)
+        assertEquals(Lifecycle.State.CREATED, child4.lifecycle.state)
+        assertEquals(Lifecycle.State.RESUMED, child5.lifecycle.state)
+    }
+
+    @Test
+    fun WHEN_items_and_active_items_changed_THEN_get_returns_components() {
+        val items =
+            ctx.childLazyItems<Int>(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        nav.navigate {
+            it.copy(
+                items = listOf(1, 6, 7, 8, 9),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.RESUMED,
+                    7 to ActiveLifecycleState.CREATED,
+                    8 to ActiveLifecycleState.STARTED,
+                    9 to ActiveLifecycleState.RESUMED,
+                )
+            )
+        }
+
+        val instances = items.value.items.map(items::get)
+
+        assertEquals(listOf(1, 6, 7, 8, 9), instances.map(Component<Int>::config))
+    }
+
+    @Test
+    fun WHEN_items_and_active_items_changed_THEN_get_returns_same_components() {
+        val items =
+            ctx.childLazyItems<Int>(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        nav.navigate {
+            it.copy(
+                items = listOf(1, 6, 7, 8, 9),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.RESUMED,
+                    7 to ActiveLifecycleState.CREATED,
+                    8 to ActiveLifecycleState.STARTED,
+                    9 to ActiveLifecycleState.RESUMED,
+                )
+            )
+        }
+
+        val instances1 = items.value.items.map(items::get)
+        val instances2 = items.value.items.map(items::get)
+
+        assertEquals(instances1, instances2)
+    }
+
+    @Test
+    fun WHEN_items_and_active_items_changed_THEN_get_returns_components_with_correct_lifecycles() {
+        val items =
+            ctx.childLazyItems<Int>(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        nav.navigate {
+            it.copy(
+                items = listOf(1, 6, 7, 8, 9),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.RESUMED,
+                    7 to ActiveLifecycleState.CREATED,
+                    8 to ActiveLifecycleState.STARTED,
+                    9 to ActiveLifecycleState.RESUMED,
+                )
+            )
+        }
+
+        val (child1, child6, child7, child8, child9) = items.value.items.map(items::get)
+
+        assertEquals(Lifecycle.State.RESUMED, child1.lifecycle.state)
+        assertEquals(Lifecycle.State.CREATED, child6.lifecycle.state)
+        assertEquals(Lifecycle.State.CREATED, child7.lifecycle.state)
+        assertEquals(Lifecycle.State.STARTED, child8.lifecycle.state)
+        assertEquals(Lifecycle.State.RESUMED, child9.lifecycle.state)
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsNavigationTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsNavigationTest.kt
new file mode 100644
index 00000000..0e55e83f
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsNavigationTest.kt
@@ -0,0 +1,217 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.TestComponentContext
+import com.arkivanov.decompose.router.items.LazyComponentState.*
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+import kotlin.test.Test
+
+@Suppress("TestFunctionName")
+class LazyItemsNavigationTest {
+
+    private val navigation = ItemsNavigation<Int>()
+    private val context = TestComponentContext()
+
+    @Test
+    fun WHEN_created_with_initial_items_THEN_all_children_pending() {
+        val items = context.childLazyItems(source = navigation, initialItems = listOf(1, 2, 3))
+
+        items.assertItems(1 to PENDING, 2 to PENDING, 3 to PENDING)
+    }
+
+    @Test
+    fun GIVEN_initial_items_WHEN_more_items_added_THEN_all_children_pending() {
+        val items = context.childLazyItems(source = navigation, initialItems = listOf(1, 2, 3))
+
+        navigation.navigate { it.copy(items = listOf(1, 2, 3, 4, 5, 6)) }
+
+        items.assertItems(1 to PENDING, 2 to PENDING, 3 to PENDING, 4 to PENDING, 5 to PENDING, 6 to PENDING)
+    }
+
+    @Test
+    fun GIVEN_initial_items_WHEN_some_last_items_removed_THEN_all_children_pending() {
+        val items = context.childLazyItems(source = navigation, initialItems = listOf(1, 2, 3, 4, 5, 6))
+
+        navigation.navigate { it.copy(items = it.items.dropLast(3)) }
+
+        items.assertItems(1 to PENDING, 2 to PENDING, 3 to PENDING)
+    }
+
+    @Test
+    fun GIVEN_initial_items_WHEN_some_first_items_removed_THEN_all_children_pending() {
+        val items = context.childLazyItems(source = navigation, initialItems = listOf(1, 2, 3, 4, 5, 6))
+
+        navigation.navigate { it.copy(items = it.items.drop(3)) }
+
+        items.assertItems(4 to PENDING, 5 to PENDING, 6 to PENDING)
+    }
+
+    @Test
+    fun GIVEN_initial_items_WHEN_some_items_removed_THEN_all_children_pending() {
+        val items = context.childLazyItems(source = navigation, initialItems = listOf(1, 2, 3, 4, 5, 6))
+
+        navigation.navigate { it.copy(items = listOf(1, 3, 5)) }
+
+        items.assertItems(1 to PENDING, 3 to PENDING, 5 to PENDING)
+    }
+
+    @Test
+    fun WHEN_initial_items_and_some_active_items_THEN_active_children_instantiated_and_others_pending() {
+        val items =
+            context.childLazyItems(
+                source = navigation,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.assertItems(1 to PENDING, 2 to CREATED, 3 to STARTED, 4 to RESUMED, 5 to PENDING)
+    }
+
+    @Test
+    fun GIVEN_initial_items_WHEN_some_items_activated_THEN_active_children_instantiated_and_others_pending() {
+        val items = context.childLazyItems(source = navigation, initialItems = listOf(1, 2, 3, 4, 5))
+
+        navigation.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+        }
+
+        items.assertItems(1 to PENDING, 2 to CREATED, 3 to STARTED, 4 to RESUMED, 5 to PENDING)
+    }
+
+    @Test
+    fun GIVEN_initial_items_and_some_active_items_WHEN_some_active_items_changed_THEN_active_children_instantiated_and_others_destroyed() {
+        val items =
+            context.childLazyItems(
+                source = navigation,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        navigation.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    5 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+        }
+
+        items.assertItems(1 to CREATED, 2 to DESTROYED, 3 to STARTED, 4 to DESTROYED, 5 to RESUMED)
+    }
+
+    @Test
+    fun GIVEN_initial_items_and_all_active_items_WHEN_some_last_items_removed_THEN_active_children_instantiated_and_others_removed() {
+        val items =
+            context.childLazyItems(
+                source = navigation,
+                initialItems = listOf(1, 2, 3, 4, 5),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                    4 to ActiveLifecycleState.STARTED,
+                    5 to ActiveLifecycleState.CREATED,
+                ),
+            )
+
+        navigation.navigate { it.copy(items = listOf(1, 2, 3)) }
+
+        items.assertItems(1 to CREATED, 2 to STARTED, 3 to RESUMED, 4 to REMOVED, 5 to REMOVED)
+    }
+
+    @Test
+    fun GIVEN_initial_and_active_items_WHEN_items_and_active_items_changed_THEN_active_instantiated_and_missing_removed_and_new_pending() {
+        val items =
+            context.childLazyItems(
+                source = navigation,
+                initialItems = listOf(1, 2, 3, 4, 5, 6, 7, 8),
+                activeItems = mapOf(
+                    2 to ActiveLifecycleState.CREATED,
+                    3 to ActiveLifecycleState.STARTED,
+                    4 to ActiveLifecycleState.RESUMED,
+                    5 to ActiveLifecycleState.STARTED,
+                    6 to ActiveLifecycleState.CREATED,
+                    7 to ActiveLifecycleState.STARTED,
+                    8 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        navigation.navigate {
+            it.copy(
+                items = listOf(3, 4, 5, 6, 7, 9, 10, 11, 12),
+                activeItems = mapOf(
+                    4 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.STARTED,
+                    6 to ActiveLifecycleState.RESUMED,
+                    9 to ActiveLifecycleState.RESUMED,
+                    10 to ActiveLifecycleState.STARTED,
+                    11 to ActiveLifecycleState.CREATED,
+                ),
+            )
+        }
+
+        items.assertItems(
+            2 to REMOVED,
+            3 to DESTROYED,
+            4 to CREATED,
+            5 to STARTED,
+            6 to RESUMED,
+            7 to DESTROYED,
+            8 to REMOVED,
+            9 to RESUMED,
+            10 to STARTED,
+            11 to CREATED,
+            12 to PENDING,
+        )
+    }
+
+    @Test
+    fun GIVEN_initial_items_and_all_active_items_WHEN_all_items_removed_THEN_all_children_removed() {
+        val items =
+            context.childLazyItems(
+                source = navigation,
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        navigation.navigate { it.copy(items = emptyList()) }
+
+        items.assertItems(1 to REMOVED, 2 to REMOVED, 3 to REMOVED)
+    }
+
+    @Test
+    fun GIVEN_initial_items_and_all_active_WHEN_all_active_items_removed_THEN_all_children_destroyed() {
+        val items =
+            context.childLazyItems(
+                source = navigation,
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        navigation.navigate { it.copy(activeItems = emptyMap()) }
+
+        items.assertItems(1 to DESTROYED, 2 to DESTROYED, 3 to DESTROYED)
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsRetainedInstanceTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsRetainedInstanceTest.kt
new file mode 100644
index 00000000..222b7882
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsRetainedInstanceTest.kt
@@ -0,0 +1,366 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.TestComponentContext
+import com.arkivanov.decompose.recreate
+import com.arkivanov.decompose.router.TestInstance
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+import com.arkivanov.essenty.instancekeeper.getOrCreate
+import kotlin.test.*
+
+@Suppress("TestFunctionName")
+class LazyItemsRetainedInstanceTest {
+
+    @Test
+    fun WHEN_some_items_removed_THEN_remaining_active_instances_not_destroyed() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5, 6),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                    4 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.STARTED,
+                    6 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val instance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val instance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        nav.navigate { it.copy(items = it.items - listOf(4, 5, 6)) }
+
+        assertFalse(instance1.isDestroyed)
+        assertFalse(instance2.isDestroyed)
+        assertFalse(instance3.isDestroyed)
+    }
+
+    @Test
+    fun WHEN_some_items_removed_THEN_remaining_active_instances_not_recreated() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5, 6),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                    4 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.STARTED,
+                    6 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val instance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val instance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        nav.navigate { it.copy(items = it.items - listOf(4, 5, 6)) }
+
+        val newInstance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val newInstance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val newInstance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        assertSame(instance1, newInstance1)
+        assertSame(instance2, newInstance2)
+        assertSame(instance3, newInstance3)
+    }
+
+    @Test
+    fun WHEN_some_items_removed_THEN_removed_instances_destroyed() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5, 6),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                    4 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.STARTED,
+                    6 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instance4 = items.require(4).instanceKeeper.getOrCreate { TestInstance() }
+        val instance5 = items.require(5).instanceKeeper.getOrCreate { TestInstance() }
+        val instance6 = items.require(6).instanceKeeper.getOrCreate { TestInstance() }
+
+        nav.navigate { it.copy(items = it.items - listOf(4, 5, 6)) }
+
+        assertTrue(instance4.isDestroyed)
+        assertTrue(instance5.isDestroyed)
+        assertTrue(instance6.isDestroyed)
+    }
+
+    @Test
+    fun WHEN_some_items_deactivated_THEN_remaining_active_instances_not_destroyed() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5, 6),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                    4 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.STARTED,
+                    6 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val instance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val instance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        nav.navigate { it.copy(activeItems = it.activeItems - setOf(4, 5, 6)) }
+
+        assertFalse(instance1.isDestroyed)
+        assertFalse(instance2.isDestroyed)
+        assertFalse(instance3.isDestroyed)
+    }
+
+    @Test
+    fun WHEN_some_items_deactivated_THEN_remaining_active_instances_not_recreated() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5, 6),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                    4 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.STARTED,
+                    6 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val instance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val instance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        nav.navigate { it.copy(activeItems = it.activeItems - setOf(4, 5, 6)) }
+
+        val newInstance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val newInstance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val newInstance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        assertSame(instance1, newInstance1)
+        assertSame(instance2, newInstance2)
+        assertSame(instance3, newInstance3)
+    }
+
+    @Test
+    fun WHEN_some_items_deactivated_THEN_deactivated_instances_destroyed() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3, 4, 5, 6),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                    4 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.STARTED,
+                    6 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instance4 = items.require(4).instanceKeeper.getOrCreate { TestInstance() }
+        val instance5 = items.require(5).instanceKeeper.getOrCreate { TestInstance() }
+        val instance6 = items.require(6).instanceKeeper.getOrCreate { TestInstance() }
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+        }
+
+        assertTrue(instance4.isDestroyed)
+        assertTrue(instance5.isDestroyed)
+        assertTrue(instance6.isDestroyed)
+    }
+
+    @Test
+    fun WHEN_configuration_change_THEN_active_instances_not_destroyed() {
+        var ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val instance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val instance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        ctx = ctx.recreate(isConfigurationChange = true)
+        ctx.childLazyItems<Int>()
+
+        assertFalse(instance1.isDestroyed)
+        assertFalse(instance2.isDestroyed)
+        assertFalse(instance3.isDestroyed)
+    }
+
+    @Test
+    fun WHEN_configuration_change_THEN_active_instances_not_recreated() {
+        var ctx = TestComponentContext()
+
+        var items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val instance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val instance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val instance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        ctx = ctx.recreate(isConfigurationChange = true)
+        items = ctx.childLazyItems()
+
+        val newInstance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val newInstance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val newInstance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        assertSame(instance1, newInstance1)
+        assertSame(instance2, newInstance2)
+        assertSame(instance3, newInstance3)
+    }
+
+    @Test
+    fun GIVEN_without_state_saver_WHEN_configuration_change_without_initial_state_THEN_active_instances_destroyed() {
+        var ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+                stateSaver = null,
+            )
+
+        val instance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val instance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val instance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        ctx = ctx.recreate(isConfigurationChange = true)
+        ctx.childLazyItems<Int>()
+
+        assertTrue(instance1.isDestroyed)
+        assertTrue(instance2.isDestroyed)
+        assertTrue(instance3.isDestroyed)
+    }
+
+    @Test
+    fun GIVEN_without_state_saver_WHEN_configuration_change_with_same_initial_state_THEN_active_instances_destroyed() {
+        var ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+                stateSaver = null,
+            )
+
+        val instance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val instance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val instance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        ctx = ctx.recreate(isConfigurationChange = true)
+
+        ctx.childLazyItems<Int>(
+            initialItems = listOf(1, 2, 3),
+            activeItems = mapOf(
+                1 to ActiveLifecycleState.CREATED,
+                2 to ActiveLifecycleState.STARTED,
+                3 to ActiveLifecycleState.RESUMED,
+            ),
+        )
+
+        assertTrue(instance1.isDestroyed)
+        assertTrue(instance2.isDestroyed)
+        assertTrue(instance3.isDestroyed)
+    }
+
+    @Test
+    fun GIVEN_without_state_saver_WHEN_configuration_change_with_same_initial_state_THEN_active_instances_recreated() {
+        var ctx = TestComponentContext()
+
+        var items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+                stateSaver = null,
+            )
+
+        val instance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val instance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val instance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        ctx = ctx.recreate(isConfigurationChange = true)
+
+        items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val newInstance1 = items.require(1).instanceKeeper.getOrCreate { TestInstance() }
+        val newInstance2 = items.require(2).instanceKeeper.getOrCreate { TestInstance() }
+        val newInstance3 = items.require(3).instanceKeeper.getOrCreate { TestInstance() }
+
+        assertNotSame(instance1, newInstance1)
+        assertNotSame(instance2, newInstance2)
+        assertNotSame(instance3, newInstance3)
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsSavedStateTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsSavedStateTest.kt
new file mode 100644
index 00000000..b1d6f5d6
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/LazyItemsSavedStateTest.kt
@@ -0,0 +1,338 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.TestComponentContext
+import com.arkivanov.decompose.consume
+import com.arkivanov.decompose.recreate
+import com.arkivanov.decompose.register
+import com.arkivanov.decompose.router.children.transientNavStateSaver
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+@Suppress("TestFunctionName")
+class LazyItemsSavedStateTest {
+
+    @Test
+    fun WHEN_recreated_without_initial_state_THEN_active_child_state_restored() {
+        var ctx = TestComponentContext()
+
+        var items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.require(1).stateKeeper.register(key = "key") { 1 }
+        items.require(2).stateKeeper.register(key = "key") { 2 }
+        items.require(3).stateKeeper.register(key = "key") { 3 }
+
+        ctx = ctx.recreate()
+        items = ctx.childLazyItems()
+
+        val state1 = items.require(1).stateKeeper.consume<Int>(key = "key")
+        val state2 = items.require(2).stateKeeper.consume<Int>(key = "key")
+        val state3 = items.require(3).stateKeeper.consume<Int>(key = "key")
+
+        assertEquals(1, state1)
+        assertEquals(2, state2)
+        assertEquals(3, state3)
+    }
+
+    @Test
+    fun WHEN_recreated_with_same_initial_state_THEN_active_child_state_restored() {
+        var ctx = TestComponentContext()
+
+        var items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+                stateSaver = null,
+            )
+
+        items.require(1).stateKeeper.register(key = "key") { 1 }
+        items.require(2).stateKeeper.register(key = "key") { 2 }
+        items.require(3).stateKeeper.register(key = "key") { 3 }
+
+        ctx = ctx.recreate()
+
+        items =
+            ctx.childLazyItems(
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        val state1 = items.require(1).stateKeeper.consume<Int>(key = "key")
+        val state2 = items.require(2).stateKeeper.consume<Int>(key = "key")
+        val state3 = items.require(3).stateKeeper.consume<Int>(key = "key")
+
+        assertNull(state1)
+        assertNull(state2)
+        assertNull(state3)
+    }
+
+    @Test
+    fun WHEN_recreated_without_initial_state_THEN_navigation_state_restored() {
+        var ctx = TestComponentContext()
+
+        ctx.childLazyItems(
+            initialItems = listOf(1, 2, 3, 4),
+            activeItems = mapOf(
+                1 to ActiveLifecycleState.CREATED,
+                2 to ActiveLifecycleState.STARTED,
+                3 to ActiveLifecycleState.RESUMED,
+            ),
+        )
+
+        ctx = ctx.recreate()
+        val items = ctx.childLazyItems<Int>()
+
+        items.assertItems(
+            1 to LazyComponentState.CREATED,
+            2 to LazyComponentState.STARTED,
+            3 to LazyComponentState.RESUMED,
+            4 to LazyComponentState.PENDING,
+        )
+    }
+
+    @Test
+    fun GIVEN_transientNavStateSaver_WHEN_configuration_change_without_initial_state_THEN_navigation_state_restored() {
+        var ctx = TestComponentContext()
+
+        ctx.childLazyItems(
+            initialItems = listOf(1, 2, 3, 4),
+            activeItems = mapOf(
+                1 to ActiveLifecycleState.CREATED,
+                2 to ActiveLifecycleState.STARTED,
+                3 to ActiveLifecycleState.RESUMED,
+            ),
+            stateSaver = transientNavStateSaver(),
+        )
+
+        ctx = ctx.recreate(isConfigurationChange = true)
+        val items = ctx.childLazyItems<Int>(stateSaver = transientNavStateSaver())
+
+        items.assertItems(
+            1 to LazyComponentState.CREATED,
+            2 to LazyComponentState.STARTED,
+            3 to LazyComponentState.RESUMED,
+            4 to LazyComponentState.PENDING,
+        )
+    }
+
+    @Test
+    fun GIVEN_stateSaver_null_WHEN_recreated_with_another_initial_state_THEN_new_navigation_state() {
+        var ctx = TestComponentContext()
+
+        ctx.childLazyItems(
+            initialItems = listOf(1, 2, 3),
+            activeItems = mapOf(
+                1 to ActiveLifecycleState.CREATED,
+                2 to ActiveLifecycleState.STARTED,
+                3 to ActiveLifecycleState.RESUMED,
+            ),
+            stateSaver = null,
+        )
+
+        ctx = ctx.recreate()
+
+        val items =
+            ctx.childLazyItems<Int>(
+                initialItems = listOf(4, 5, 6),
+                activeItems = mapOf(
+                    4 to ActiveLifecycleState.CREATED,
+                    5 to ActiveLifecycleState.STARTED,
+                    6 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.assertItems(
+            4 to LazyComponentState.CREATED,
+            5 to LazyComponentState.STARTED,
+            6 to LazyComponentState.RESUMED,
+        )
+    }
+
+    @Test
+    fun WHEN_items_deactivated_and_activated_again_THEN_child_state_restored() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.require(1).stateKeeper.register(key = "key") { 1 }
+        items.require(2).stateKeeper.register(key = "key") { 2 }
+        items.require(3).stateKeeper.register(key = "key") { 3 }
+        nav.navigate { it.copy(activeItems = emptyMap()) }
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.RESUMED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.CREATED,
+                ),
+            )
+        }
+
+        val state1 = items.require(1).stateKeeper.consume<Int>(key = "key")
+        val state2 = items.require(2).stateKeeper.consume<Int>(key = "key")
+        val state3 = items.require(3).stateKeeper.consume<Int>(key = "key")
+
+        assertEquals(1, state1)
+        assertEquals(2, state2)
+        assertEquals(3, state3)
+    }
+
+    @Test
+    fun WHEN_items_removed_and_added_active_again_THEN_child_state_not_restored() {
+        val nav = ItemsNavigation<Int>()
+        val ctx = TestComponentContext()
+
+        val items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.require(1).stateKeeper.register(key = "key") { 1 }
+        items.require(2).stateKeeper.register(key = "key") { 2 }
+        items.require(3).stateKeeper.register(key = "key") { 3 }
+        nav.navigate { it.copy(items = emptyList()) }
+
+        nav.navigate {
+            it.copy(
+                items = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.RESUMED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.CREATED,
+                ),
+            )
+        }
+
+        val state1 = items.require(1).stateKeeper.consume<Int>(key = "key")
+        val state2 = items.require(2).stateKeeper.consume<Int>(key = "key")
+        val state3 = items.require(3).stateKeeper.consume<Int>(key = "key")
+
+        assertNull(state1)
+        assertNull(state2)
+        assertNull(state3)
+    }
+
+    @Test
+    fun WHEN_items_deactivated_and_recreated_and_same_items_activated_THEN_child_state_restored() {
+        var nav = ItemsNavigation<Int>()
+        var ctx = TestComponentContext()
+
+        var items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.require(1).stateKeeper.register(key = "key") { 1 }
+        items.require(2).stateKeeper.register(key = "key") { 2 }
+        items.require(3).stateKeeper.register(key = "key") { 3 }
+        nav.navigate { it.copy(activeItems = emptyMap()) }
+
+        nav = ItemsNavigation()
+        ctx = ctx.recreate()
+        items = ctx.childLazyItems(source = nav)
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+        }
+
+        val state1 = items.require(1).stateKeeper.consume<Int>(key = "key")
+        val state2 = items.require(2).stateKeeper.consume<Int>(key = "key")
+        val state3 = items.require(3).stateKeeper.consume<Int>(key = "key")
+
+        assertEquals(1, state1)
+        assertEquals(2, state2)
+        assertEquals(3, state3)
+    }
+
+    @Test
+    fun WHEN_items_deactivated_and_recreated_twice_and_same_items_activated_THEN_child_state_restored() {
+        var nav = ItemsNavigation<Int>()
+        var ctx = TestComponentContext()
+
+        var items =
+            ctx.childLazyItems(
+                source = nav,
+                initialItems = listOf(1, 2, 3),
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+
+        items.require(1).stateKeeper.register(key = "key") { 1 }
+        items.require(2).stateKeeper.register(key = "key") { 2 }
+        items.require(3).stateKeeper.register(key = "key") { 3 }
+        nav.navigate { it.copy(activeItems = emptyMap()) }
+
+        ctx = ctx.recreate()
+        ctx.childLazyItems<Int>()
+        nav = ItemsNavigation()
+        ctx = ctx.recreate()
+        items = ctx.childLazyItems(source = nav)
+
+        nav.navigate {
+            it.copy(
+                activeItems = mapOf(
+                    1 to ActiveLifecycleState.CREATED,
+                    2 to ActiveLifecycleState.STARTED,
+                    3 to ActiveLifecycleState.RESUMED,
+                ),
+            )
+        }
+
+        val state1 = items.require(1).stateKeeper.consume<Int>(key = "key")
+        val state2 = items.require(2).stateKeeper.consume<Int>(key = "key")
+        val state3 = items.require(3).stateKeeper.consume<Int>(key = "key")
+
+        assertEquals(1, state1)
+        assertEquals(2, state2)
+        assertEquals(3, state3)
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/TestLazyChildItems.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/TestLazyChildItems.kt
new file mode 100644
index 00000000..37731356
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/items/TestLazyChildItems.kt
@@ -0,0 +1,140 @@
+package com.arkivanov.decompose.router.items
+
+import com.arkivanov.decompose.Cancellation
+import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.router.Component
+import com.arkivanov.decompose.router.children.NavStateSaver
+import com.arkivanov.decompose.router.children.NavigationSource
+import com.arkivanov.decompose.router.items.Items.ActiveLifecycleState
+import com.arkivanov.decompose.router.items.LazyComponentState.CREATED
+import com.arkivanov.decompose.router.items.LazyComponentState.DESTROYED
+import com.arkivanov.decompose.router.items.LazyComponentState.PENDING
+import com.arkivanov.decompose.router.items.LazyComponentState.REMOVED
+import com.arkivanov.decompose.router.items.LazyComponentState.RESUMED
+import com.arkivanov.decompose.router.items.LazyComponentState.STARTED
+import com.arkivanov.decompose.value.Value
+import com.arkivanov.essenty.lifecycle.Lifecycle
+import com.arkivanov.essenty.lifecycle.doOnDestroy
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.serializer
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+class TestLazyChildItems<C : Any>(
+    private val delegate: LazyChildItems<C, Component<C>>,
+    val destroyedComponents: Map<C, Component<C>>,
+    val removedComponents: Map<C, Component<C>>,
+) : LazyChildItems<C, Component<C>>(), ItemsNavigator<C> by delegate {
+
+    val components: Map<C, Component<C>> get() = value.activeItems.mapValues { it.value.first }
+
+    override val value: ChildItems<C, Component<C>> by delegate::value
+
+    override fun subscribe(observer: (ChildItems<C, Component<C>>) -> Unit): Cancellation =
+        delegate.subscribe(observer)
+
+    override fun get(configuration: C): Component<C> =
+        delegate[configuration]
+}
+
+fun <C : Any> Value<ChildItems<C, Component<C>>>.require(cfg: C): Component<C> =
+    value.activeItems.getValue(cfg).first
+
+inline fun <reified C : Any> ComponentContext.childLazyItems(
+    source: NavigationSource<ItemsNavigation.Event<C>> = ItemsNavigation(),
+    initialItems: List<C> = emptyList(),
+    activeItems: Map<C, ActiveLifecycleState> = emptyMap(),
+    stateSaver: NavStateSaver<Items<C>>? = NavStateSaver(Items.serializer(serializer<C>())),
+): TestLazyChildItems<C> {
+    val components = HashMap<C, Component<C>>()
+    val destroyedComponents = HashMap<C, Component<C>>()
+    val removedComponents = HashMap<C, Component<C>>()
+
+    val items =
+        childItems(
+            source = source,
+            stateSaver = stateSaver,
+            initialItems = { Items(items = initialItems, activeItems = activeItems) },
+            childFactory = { cfg, ctx ->
+                val component = Component(cfg, ctx)
+                components[cfg] = component
+                removedComponents -= cfg
+                destroyedComponents -= cfg
+
+                var isStateSaved = false
+                ctx.stateKeeper.register("saved_state_marker", Boolean.serializer()) {
+                    isStateSaved = true
+                    true
+                }
+
+                ctx.doOnDestroy {
+                    components -= cfg
+
+                    if (isStateSaved) {
+                        destroyedComponents[cfg] = component
+                    } else {
+                        removedComponents[cfg] = component
+                    }
+                }
+
+                component
+            },
+        )
+
+    return TestLazyChildItems(
+        delegate = items,
+        destroyedComponents = destroyedComponents,
+        removedComponents = removedComponents,
+    )
+}
+
+fun <C : Any> TestLazyChildItems<C>.assertItems(
+    vararg items: Pair<C, LazyComponentState>,
+) {
+
+    val expectedItems = items.filterNot { (_, state) -> state == REMOVED }.map { (cfg) -> cfg }
+
+    val expectedActiveItems =
+        items
+            .mapNotNull { (cfg, state) ->
+                when (state) {
+                    CREATED -> cfg to ActiveLifecycleState.CREATED
+                    STARTED -> cfg to ActiveLifecycleState.STARTED
+                    RESUMED -> cfg to ActiveLifecycleState.RESUMED
+                    else -> null
+                }
+            }
+            .toMap()
+
+    assertEquals(expectedItems, value.items)
+    assertEquals(expectedActiveItems, value.activeItems.mapValues { it.value.second })
+    assertEquals(expectedActiveItems.keys.toList(), value.activeItems.values.map { it.first.config })
+
+    val destroyedComponentCount = items.count { (_, state) -> state == DESTROYED }
+    assertEquals(destroyedComponentCount, destroyedComponents.size)
+
+    val removedComponentCount = items.count { (_, state) -> state == REMOVED }
+    assertEquals(removedComponentCount, removedComponents.size)
+
+    items.filterNot { (_, state) -> state == PENDING }.forEach { (cfg, state) ->
+        val component =
+            when (state) {
+                REMOVED -> removedComponents[cfg]
+                DESTROYED -> destroyedComponents[cfg]
+                else -> components[cfg]
+            }
+
+        assertNotNull(actual = component, message = "Component not found: $cfg, $state")
+        assertEquals(state.toLifecycleState(), component.lifecycle.state)
+    }
+}
+
+private fun LazyComponentState.toLifecycleState(): Lifecycle.State =
+    when (this) {
+        PENDING -> error("Invalid component state: $this")
+        REMOVED -> Lifecycle.State.DESTROYED
+        DESTROYED -> Lifecycle.State.DESTROYED
+        CREATED -> Lifecycle.State.CREATED
+        STARTED -> Lifecycle.State.STARTED
+        RESUMED -> Lifecycle.State.RESUMED
+    }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt
index f6b244b4..8614e623 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt
@@ -5,6 +5,13 @@ import com.arkivanov.decompose.DecomposeExperimentFlags
 import com.arkivanov.decompose.DefaultComponentContext
 import com.arkivanov.decompose.DelicateDecomposeApi
 import com.arkivanov.decompose.consume
+import com.arkivanov.decompose.lifecycle.TestLifecycleCallbacks
+import com.arkivanov.decompose.lifecycle.TestLifecycleCallbacks.Event.ON_CREATE
+import com.arkivanov.decompose.lifecycle.TestLifecycleCallbacks.Event.ON_DESTROY
+import com.arkivanov.decompose.lifecycle.TestLifecycleCallbacks.Event.ON_PAUSE
+import com.arkivanov.decompose.lifecycle.TestLifecycleCallbacks.Event.ON_RESUME
+import com.arkivanov.decompose.lifecycle.TestLifecycleCallbacks.Event.ON_START
+import com.arkivanov.decompose.lifecycle.TestLifecycleCallbacks.Event.ON_STOP
 import com.arkivanov.decompose.register
 import com.arkivanov.decompose.router.TestInstance
 import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
@@ -420,6 +427,108 @@ class ChildStackIntegrationTest {
         assertFalse(instance.isDestroyed)
     }
 
+    @Test
+    fun WHEN_created_with_multiple_children_THEN_lifecycles_events_in_correct_order() {
+        val events = ArrayList<Pair<Int, TestLifecycleCallbacks.Event>>()
+
+        context.childStack(initialStack = listOf(Config(1), Config(2), Config(3))) { cfg, ctx ->
+            ctx.lifecycle.subscribe(TestLifecycleCallbacks { events += cfg.id to it })
+            Component(cfg, ctx)
+        }
+
+        assertEquals(listOf(1 to ON_CREATE, 2 to ON_CREATE, 3 to ON_CREATE, 3 to ON_START, 3 to ON_RESUME), events)
+    }
+
+    @Test
+    fun WHEN_child_pushed_THEN_lifecycles_events_in_correct_order() {
+        val events = ArrayList<Pair<Int, TestLifecycleCallbacks.Event>>()
+
+        context.childStack(initialStack = listOf(Config(1))) { cfg, ctx ->
+            ctx.lifecycle.subscribe(TestLifecycleCallbacks { events += cfg.id to it })
+            Component(cfg, ctx)
+        }
+
+        events.clear()
+
+        navigation.push(Config(2))
+
+        assertEquals(listOf(2 to ON_CREATE, 1 to ON_PAUSE, 1 to ON_STOP, 2 to ON_START, 2 to ON_RESUME), events)
+    }
+
+    @Test
+    fun WHEN_child_popped_THEN_lifecycles_events_in_correct_order() {
+        val events = ArrayList<Pair<Int, TestLifecycleCallbacks.Event>>()
+
+        context.childStack(initialStack = listOf(Config(1), Config(2))) { cfg, ctx ->
+            ctx.lifecycle.subscribe(TestLifecycleCallbacks { events += cfg.id to it })
+            Component(cfg, ctx)
+        }
+
+        events.clear()
+
+        navigation.pop()
+
+        assertEquals(listOf(2 to ON_PAUSE, 2 to ON_STOP, 2 to ON_DESTROY, 1 to ON_START, 1 to ON_RESUME), events)
+    }
+
+    @Test
+    fun WHEN_child_replaced_THEN_lifecycles_events_in_correct_order() {
+        val events = ArrayList<Pair<Int, TestLifecycleCallbacks.Event>>()
+
+        context.childStack(initialStack = listOf(Config(1))) { cfg, ctx ->
+            ctx.lifecycle.subscribe(TestLifecycleCallbacks { events += cfg.id to it })
+            Component(cfg, ctx)
+        }
+
+        events.clear()
+        navigation.navigate { listOf(Config(2)) }
+
+        assertEquals(listOf(2 to ON_CREATE, 1 to ON_PAUSE, 1 to ON_STOP, 1 to ON_DESTROY, 2 to ON_START, 2 to ON_RESUME), events)
+    }
+
+    @Test
+    fun WHEN_multiple_children_popped_THEN_lifecycles_events_in_correct_order() {
+        val events = ArrayList<Pair<Int, TestLifecycleCallbacks.Event>>()
+
+        context.childStack(initialStack = listOf(Config(1), Config(2), Config(3))) { cfg, ctx ->
+            ctx.lifecycle.subscribe(TestLifecycleCallbacks { events += cfg.id to it })
+            Component(cfg, ctx)
+        }
+
+        events.clear()
+
+        navigation.navigate { listOf(Config(1)) }
+
+        assertEquals(listOf(2 to ON_DESTROY, 3 to ON_PAUSE, 3 to ON_STOP, 3 to ON_DESTROY, 1 to ON_START, 1 to ON_RESUME), events)
+    }
+
+    @Test
+    fun WHEN_multiple_children_popped_and_pushed_THEN_lifecycles_events_in_correct_order() {
+        val events = ArrayList<Pair<Int, TestLifecycleCallbacks.Event>>()
+
+        context.childStack(initialStack = listOf(Config(1), Config(2), Config(3))) { cfg, ctx ->
+            ctx.lifecycle.subscribe(TestLifecycleCallbacks { events += cfg.id to it })
+            Component(cfg, ctx)
+        }
+
+        events.clear()
+
+        navigation.navigate { listOf(Config(1), Config(4), Config(5)) }
+
+        assertEquals(
+            listOf(
+                4 to ON_CREATE,
+                5 to ON_CREATE,
+                2 to ON_DESTROY,
+                3 to ON_PAUSE,
+                3 to ON_STOP,
+                3 to ON_DESTROY,
+                5 to ON_START,
+                5 to ON_RESUME,
+            ), events
+        )
+    }
+
     @Test
     fun WHEN_push_duplicated_children_THEN_stack_contains_duplicated_children() {
         DecomposeExperimentFlags.duplicateConfigurationsEnabled = true
@@ -445,13 +554,14 @@ class ChildStackIntegrationTest {
     private fun ComponentContext.childStack(
         initialStack: List<Config> = emptyList(),
         persistent: Boolean = true,
+        childFactory: (Config, ComponentContext) -> Component = ::Component,
     ): Value<ChildStack<Config, Component>> =
         childStack(
             source = navigation,
             serializer = Config.serializer().takeIf { persistent },
             initialStack = { initialStack },
             handleBackButton = true,
-            childFactory = ::Component,
+            childFactory = childFactory,
         )
 
     private val ChildStack<Config, Component>.children: List<Pair<Int, Int>>