Difference Between SlotNavigation and StackNavigation in Decompose

Both navigation types implement the NavigationSource contract but manage fundamentally different state models: StackNavigation maintains a List<C> back-stack for layered screens, while SlotNavigation manages a single optional C? configuration for mutually exclusive UI elements like dialogs.

In the arkivanov/decompose library, these two router implementations provide the foundation for navigation in Kotlin Multiplatform applications. Understanding the architectural distinction between managing a stack of configurations versus a single optional slot is essential for choosing the right navigation pattern for your UI components.

Core Architectural Distinction

The primary difference lies in what each navigation type represents and navigates. According to the Decompose source code, both implement NavigationSource<Event<*>> and combine a navigator interface with event observation via a Relay, but they target different navigation paradigms.

StackNavigation works with a StackNavigator<C> that operates on a list of configurations (List<C>). This represents a traditional back-stack where the last element is the currently visible screen, and previous elements remain in the background. The stack must never be empty—there is always at least one active configuration.

SlotNavigation works with a SlotNavigator<C> that operates on a single optional configuration (C?). This represents a slot that either holds one active configuration or null (indicating no child is shown). Only one child can exist at a time, making it ideal for overlays and temporary UI.

State Models and Transformers

The transformer signatures reveal the structural difference in how state mutations occur.

In router/stack/StackNavigator.kt, the navigate method accepts:

transformer: (stack: List<C>) -> List<C>

This allows complex stack manipulations—pushing new elements, popping the last item, replacing the entire stack, or reordering configurations. The transformer receives the current stack and returns the new desired state.

In router/slot/SlotNavigator.kt, the transformer is simpler:

transformer: (configuration: C?) -> C?

This binary state (present or absent) limits operations to showing a new configuration (replacing the current one) or hiding it (returning null). You cannot maintain a history of previous slots; switching configurations destroys the previous child immediately.

Lifecycle Handling and Behavior

Decompose handles component lifecycles differently for each navigation type based on their state models.

For StackNavigation, as implemented in DefaultStackNavigation.kt, Decompose ensures only the top configuration’s component remains in the resumed state. When you push a new configuration, the previous top component enters the stopped state but remains in the back-stack. When you pop, the top component is destroyed and the previous one resumes.

For SlotNavigation, implemented in DefaultSlotNavigation.kt, the lifecycle is more absolute. When the transformer returns a new non-null configuration, Decompose creates and resumes that component, simultaneously destroying any previous configuration. When the transformer returns null, the current component is destroyed and no replacement occurs, leaving the slot empty.

Practical Code Examples

Stack Navigation: Pushing and Popping Screens

The following example from router/stack/StackNavigation.kt demonstrates pushing a detail screen onto the stack:

val stackNavigation: StackNavigation<Config> = StackNavigation()

// Push a new screen
stackNavigation.navigate(
    transformer = { stack -> stack + Config.Detail(id = 42) },
    onComplete = { newStack, oldStack -> 
        println("Changed from $oldStack to $newStack") 
    }
)

To pop the top screen while ensuring the stack never empties:

stackNavigation.navigate(
    transformer = { stack -> 
        if (stack.size > 1) stack.dropLast(1) else stack 
    }
)

Slot Navigation: Showing and Dismissing Dialogs

For modal dialogs or bottom sheets, use SlotNavigation from router/slot/SlotNavigation.kt:

val slotNavigation: SlotNavigation<DialogConfig> = SlotNavigation()

// Show a dialog
slotNavigation.navigate(
    transformer = { _ -> DialogConfig.Alert(message = "Confirm?") }
)

To dismiss the dialog and return to the previous UI state:

slotNavigation.navigate(
    transformer = { _ -> null }
)

Observing Navigation Events

Both implementations expose navigation events via the NavigationSource interface. You can subscribe to changes for logging or side effects:

stackNavigation.subscribe { event ->
    // Access the transformer or completion callback
    println("Stack navigation event received")
}

Key Source Files

Understanding the implementation requires examining these specific files in the decompose/src/commonMain/kotlin/com/arkivanov/decompose/ directory:

Both default implementations push events into a Relay, enabling reactive navigation where UI components can observe changes without tight coupling to the navigator instances.

Summary

  • StackNavigation manages a List<C> back-stack suitable for hierarchical screen flows with history, where the last element represents the active screen.
  • SlotNavigation manages a single C? configuration ideal for dialogs, modals, and mutually exclusive UI that either exists or is absent.
  • Transformers differ in signature: stack transformers receive and return lists, while slot transformers handle nullable single values.
  • Lifecycle behavior reflects the state model—stacks maintain stopped components in the back-stack, while slots destroy the previous component immediately upon change.
  • Both implementations share the NavigationSource<Event<*>> architecture using Relay for event propagation, located in their respective router/stack and router/slot packages.

Frequently Asked Questions

When should I use SlotNavigation versus StackNavigation?

Use SlotNavigation for UI elements that overlay existing content without maintaining navigation history, such as alert dialogs, confirmation sheets, or modal bottom sheets. Use StackNavigation for primary app navigation where users expect back-button functionality and a history of visited screens, such as master-detail flows or onboarding sequences.

Can I use both navigation types in the same component?

Yes. A single Decompose component can host multiple navigation instances. You typically use StackNavigation for the main content flow and SlotNavigation for auxiliary overlays. For example, a root component might manage a stack of screens while simultaneously managing a slot for a global error dialog or loading indicator.

What happens if I return an empty list in a StackNavigation transformer?

Returning an empty list violates the contract of StackNavigation. The implementation expects at least one configuration to remain in the stack at all times. If you need to hide all content, use SlotNavigation instead, which explicitly supports null to represent the absence of any child component.

How do the onComplete callbacks differ between the two types?

In StackNavigation, the onComplete callback receives both the new and old stack states: (newStack: List<C>, oldStack: List<C>) -> Unit. In SlotNavigation, it receives the new and old configurations: (newConfig: C?, oldConfig: C?) -> Unit. Both execute after Decompose processes the state change and updates the component hierarchy.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →