
Laydr - file-based, type-safe navigation for Kotlin Compose Multiplatform and Android
If Laydr saves you time, please consider starring the repository - it helps more Compose Multiplatform and Android Compose developers find it.
Laydr is file-based, type-safe navigation for Compose Multiplatform and
Android Compose apps. Model your app as a visible routes/ directory, and
Laydr generates the Kotlin destinations, path builders, route maps,
route-local Compose entrypoints, and Nav3 wiring that usually drifts when
maintained by hand.
Use Laydr when Kotlin navigation has become a web of copied route strings, duplicated graph setup, repeated argument parsing, layout wrappers, tab registries, and stale navigation glue. Your routes become inspectable folders; Laydr generates the deterministic wiring; your app keeps Compose UI, state, data, dependency access, navigation chrome, and platform policy explicit.
Laydr supports these app targets:
| App target | Notes |
|---|---|
| Compose Multiplatform on Android | Routes live in the shared KMP module; the Android launcher and platform policy stay app-owned. |
| Compose Multiplatform on desktop/JVM | Use app-owned path state or Nav3 KMP with generated destinations. |
| Compose Multiplatform on iOS | Published KMP artifacts include iOS device and simulator targets. |
| Compose Multiplatform on web/WasmJS | WasmJS browser support is covered by the KMP route model. |
| Android-only Compose | Use src/main/kotlin/routes and Google AndroidX Navigation 3 without a shared KMP route module. |
The Gradle plugin and route generator run on JVM 17/Gradle. AndroidX adaptive scene support is not part of the current AndroidX adapter.
Navigation structure is product structure. Laydr makes that structure visible in source instead of scattering it across constants, graph builders, argument parsers, screen registries, layout wrappers, and tab setup.
routes/ tree that matches the product screens a maintainer needs to findRoute.kt, Screen.kt, and Layout.kt files beside the route
they defineBefore Laydr, one contact detail screen often needs several matching pieces:
contacts/{id}
With Laydr, the route starts as a directory, a local Route.kt, and ordinary
screen code. The generated API gives app code typed destinations and path
helpers that match the route tree.
Put route directories under the KMP module that owns app navigation:
src/commonMain/kotlin/routes/
contacts/
Route.kt
Screen.kt
by_id/
Route.kt
Screen.kt
This tree gives the app two screen routes:
routes/contacts -> /contacts -> LaydrRoutes.Contacts
routes/contacts/by_id -> /contacts/{id} ->
LaydrRoutes.Contacts.ById
Route.kt declares the route kind. Nearby Screen.kt, Layout.kt,
repositories, state holders, and UI components are ordinary app code.
Route-local rendering stays small:
// routes/contacts/by_id/Route.kt
internal val Route = LaydrRouteDef.screen(content = ::Screen)App code navigates with generated destinations:
navigator.push(
LaydrRoutes.Contacts.ById.destination(
id = LaydrRoutes.Contacts.ById.id("ada"),
),
)Apps that deliberately own path state can also use generated path helpers:
LaydrRoutes.Contacts.ById.path(
id = LaydrRoutes.Contacts.ById.id("ada"),
)Laydr supports the same route-tree model in three common Compose app shapes:
LaydrRouteHost.Start with the shape that matches your app's navigation owner. The Gradle docs show the exact setup when you are ready to wire it in.
Laydr generated source is readable Kotlin under
build/generated/laydr/<source-set>/kotlin/. KMP projects use
commonMain; Android-only projects use main. You do not edit generated
source by hand; change authored route files and rerun route checks.
The generated API includes:
LaydrRoutes for typed route objects, destinations, path builders,
appGraph, and routeMap
LaydrComposeRoutes.definitions for route-local Compose renderingLaydrRouteDef helpers for screen, layout,
screenAndLayout, and layout-value screensLaydrNavRoutes helpers for either Nav3 KMP or AndroidX Nav3
sections and stacksThe Gradle plugin wires generated source into KMP commonMain or Android-only
main, generates routes, and validates the route tree during check.
Use LaydrRouteHost when your app wants the smallest path-in/content-out
Compose host and owns path state directly.
Use laydr-nav3-kmp when JetBrains Navigation3 KMP should own back stacks,
sections, app Back, NavDisplay, payloads, route results, and optional
adaptive list/detail scenes. Laydr validates generated destinations and gives
Nav3 app code typed route values; the app still renders NavDisplay and owns
presentation policy.
Use laydr-nav3-androidx when an Android-only Compose app should use Google
AndroidX Navigation 3 without a KMP shared module. AndroidX adaptive scene
support is not part of the current AndroidX adapter.
All runtime paths use the same route-local Route.kt declarations.
Laydr is routing infrastructure, not a client architecture framework. Your app still owns:
Laydr gives those app-owned pieces stable generated route values to use.
Define the Laydr version once:
[versions]
laydr = "LAYDR_VERSION"
[plugins]
laydr = { id = "dev.goquick.laydr", version.ref = "laydr" }
[libraries]
laydr-compose = { module = "dev.goquick.laydr:laydr-compose", version.ref = "laydr" }Apply the plugin in the KMP module that owns src/commonMain/kotlin/routes:
plugins {
kotlin("multiplatform")
alias(libs.plugins.laydr)
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.laydr.compose)
}
}
}
laydr {
generatedPackage.set("example.app.generated")
compose.set(true)
}Then add route-local Route.kt files and validate them:
./gradlew :shared:checkLaydrRoutesRead Getting Started for a complete first app, and Gradle for artifact coordinates, composite-build setup, task details, and generated-source locations.
The snippets use LAYDR_VERSION as the artifact version placeholder. For this
checkout, the examples are the most reliable way to inspect current behavior
before choosing a published or locally published artifact.
LaydrRouteHost.NavDisplay.src/main/kotlin/routes.Some routes need private, testable, multi-step feature state after the app has
already matched a Laydr route. laydr-workflow handles that case without
turning the private flow into separate app-addressable destinations.
Workflow is optional and route-local. It does not replace generated destinations, Nav3 stacks, tabs, deep links, platform Back, ViewModels, DI, repositories, or app-shell policy. Many screens only need generated destinations plus ordinary Compose state.
Read Route-Local Workflow when a matched route owns a private review, confirm, or wizard-style flow.
Start with the user documentation:
LaydrRoutes,
destinations, route maps, Compose definitions, and route-local helpers.Laydr is in v0 development. The docs describe current supported behavior in this checkout and avoid planned adapters, migration history, deprecated alternatives, and maintainer-only workflows.
If Laydr saves you time, please consider starring the repository - it helps more Compose Multiplatform and Android Compose developers find it.
Laydr is file-based, type-safe navigation for Compose Multiplatform and
Android Compose apps. Model your app as a visible routes/ directory, and
Laydr generates the Kotlin destinations, path builders, route maps,
route-local Compose entrypoints, and Nav3 wiring that usually drifts when
maintained by hand.
Use Laydr when Kotlin navigation has become a web of copied route strings, duplicated graph setup, repeated argument parsing, layout wrappers, tab registries, and stale navigation glue. Your routes become inspectable folders; Laydr generates the deterministic wiring; your app keeps Compose UI, state, data, dependency access, navigation chrome, and platform policy explicit.
Laydr supports these app targets:
| App target | Notes |
|---|---|
| Compose Multiplatform on Android | Routes live in the shared KMP module; the Android launcher and platform policy stay app-owned. |
| Compose Multiplatform on desktop/JVM | Use app-owned path state or Nav3 KMP with generated destinations. |
| Compose Multiplatform on iOS | Published KMP artifacts include iOS device and simulator targets. |
| Compose Multiplatform on web/WasmJS | WasmJS browser support is covered by the KMP route model. |
| Android-only Compose | Use src/main/kotlin/routes and Google AndroidX Navigation 3 without a shared KMP route module. |
The Gradle plugin and route generator run on JVM 17/Gradle. AndroidX adaptive scene support is not part of the current AndroidX adapter.
Navigation structure is product structure. Laydr makes that structure visible in source instead of scattering it across constants, graph builders, argument parsers, screen registries, layout wrappers, and tab setup.
routes/ tree that matches the product screens a maintainer needs to findRoute.kt, Screen.kt, and Layout.kt files beside the route
they defineBefore Laydr, one contact detail screen often needs several matching pieces:
contacts/{id}
With Laydr, the route starts as a directory, a local Route.kt, and ordinary
screen code. The generated API gives app code typed destinations and path
helpers that match the route tree.
Put route directories under the KMP module that owns app navigation:
src/commonMain/kotlin/routes/
contacts/
Route.kt
Screen.kt
by_id/
Route.kt
Screen.kt
This tree gives the app two screen routes:
routes/contacts -> /contacts -> LaydrRoutes.Contacts
routes/contacts/by_id -> /contacts/{id} ->
LaydrRoutes.Contacts.ById
Route.kt declares the route kind. Nearby Screen.kt, Layout.kt,
repositories, state holders, and UI components are ordinary app code.
Route-local rendering stays small:
// routes/contacts/by_id/Route.kt
internal val Route = LaydrRouteDef.screen(content = ::Screen)App code navigates with generated destinations:
navigator.push(
LaydrRoutes.Contacts.ById.destination(
id = LaydrRoutes.Contacts.ById.id("ada"),
),
)Apps that deliberately own path state can also use generated path helpers:
LaydrRoutes.Contacts.ById.path(
id = LaydrRoutes.Contacts.ById.id("ada"),
)Laydr supports the same route-tree model in three common Compose app shapes:
LaydrRouteHost.Start with the shape that matches your app's navigation owner. The Gradle docs show the exact setup when you are ready to wire it in.
Laydr generated source is readable Kotlin under
build/generated/laydr/<source-set>/kotlin/. KMP projects use
commonMain; Android-only projects use main. You do not edit generated
source by hand; change authored route files and rerun route checks.
The generated API includes:
LaydrRoutes for typed route objects, destinations, path builders,
appGraph, and routeMap
LaydrComposeRoutes.definitions for route-local Compose renderingLaydrRouteDef helpers for screen, layout,
screenAndLayout, and layout-value screensLaydrNavRoutes helpers for either Nav3 KMP or AndroidX Nav3
sections and stacksThe Gradle plugin wires generated source into KMP commonMain or Android-only
main, generates routes, and validates the route tree during check.
Use LaydrRouteHost when your app wants the smallest path-in/content-out
Compose host and owns path state directly.
Use laydr-nav3-kmp when JetBrains Navigation3 KMP should own back stacks,
sections, app Back, NavDisplay, payloads, route results, and optional
adaptive list/detail scenes. Laydr validates generated destinations and gives
Nav3 app code typed route values; the app still renders NavDisplay and owns
presentation policy.
Use laydr-nav3-androidx when an Android-only Compose app should use Google
AndroidX Navigation 3 without a KMP shared module. AndroidX adaptive scene
support is not part of the current AndroidX adapter.
All runtime paths use the same route-local Route.kt declarations.
Laydr is routing infrastructure, not a client architecture framework. Your app still owns:
Laydr gives those app-owned pieces stable generated route values to use.
Define the Laydr version once:
[versions]
laydr = "LAYDR_VERSION"
[plugins]
laydr = { id = "dev.goquick.laydr", version.ref = "laydr" }
[libraries]
laydr-compose = { module = "dev.goquick.laydr:laydr-compose", version.ref = "laydr" }Apply the plugin in the KMP module that owns src/commonMain/kotlin/routes:
plugins {
kotlin("multiplatform")
alias(libs.plugins.laydr)
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.laydr.compose)
}
}
}
laydr {
generatedPackage.set("example.app.generated")
compose.set(true)
}Then add route-local Route.kt files and validate them:
./gradlew :shared:checkLaydrRoutesRead Getting Started for a complete first app, and Gradle for artifact coordinates, composite-build setup, task details, and generated-source locations.
The snippets use LAYDR_VERSION as the artifact version placeholder. For this
checkout, the examples are the most reliable way to inspect current behavior
before choosing a published or locally published artifact.
LaydrRouteHost.NavDisplay.src/main/kotlin/routes.Some routes need private, testable, multi-step feature state after the app has
already matched a Laydr route. laydr-workflow handles that case without
turning the private flow into separate app-addressable destinations.
Workflow is optional and route-local. It does not replace generated destinations, Nav3 stacks, tabs, deep links, platform Back, ViewModels, DI, repositories, or app-shell policy. Many screens only need generated destinations plus ordinary Compose state.
Read Route-Local Workflow when a matched route owns a private review, confirm, or wizard-style flow.
Start with the user documentation:
LaydrRoutes,
destinations, route maps, Compose definitions, and route-local helpers.Laydr is in v0 development. The docs describe current supported behavior in this checkout and avoid planned adapters, migration history, deprecated alternatives, and maintainer-only workflows.