compose-lab
Author · Open source · 2026
- Kotlin Multiplatform
- Compose Multiplatform
- SwiftUI
- Navigation 3
- SKIE
- Konsist
View source on GitHub: open github://DimiVlachos/compose-lab
Context
compose-lab is where I build the interfaces nobody asks a product team for: fourteen components rebuilt from scratch in Compose Multiplatform, each with a scripted demo and recorded as a short clip — a nav bar with a filleted cutout, a fogged mirror you wipe clear, a page-turn book, a snow globe, a reading shelf. One Kotlin codebase draws the same pixels on Android and iOS. Alongside the components sits Moodboard, a small app built to argue the opposite case: that a real Kotlin Multiplatform product should share what users never see and keep what they touch native.
Approach
The lab is a Kotlin Multiplatform library, :composeApp, with a thin :androidApp and an iOS app around it, built through two convention plugins in an included build-logic so every module is configured the same way. Each component owns one package under core/presentation/components, and each demo its own package beside it; a Konsist test fails the build if core reaches into a feature. There is no DI framework — Moodboard is wired by hand through a single MoodboardGraph object, which is honest about how small it is. Moodboard shares its data layer and ViewModels in Kotlin and bridges them to Swift with SKIE; the iOS side is SwiftUI, the Android side Material 3 Expressive on Navigation 3 with a back stack per tab. Every clip is driven by a scripted fingertip rather than my hand, so a demo can be re-recorded identically after any change.
Technical decisions
The bookshelf is the hardest piece. Each book is a six-faced box posed in 3D and projected through one camera, its pages typeset and turning under the finger. The rule that keeps it smooth: nothing recomposes while a book is in hand. The spin and the pose are read only in the draw phase, and spines, covers, pages, and window light are baked off the main thread. Drawing a page in perspective is a homography; touching it means running that projection backwards, so the finger stays on the point of the page it took hold of.
fun unproject(h: FloatArray, point: Offset): Offset? {
val a0 = h[4] * h[8] - h[5] * h[7]
val a1 = h[2] * h[7] - h[1] * h[8]
val a2 = h[1] * h[5] - h[2] * h[4]
val a3 = h[5] * h[6] - h[3] * h[8]
val a4 = h[0] * h[8] - h[2] * h[6]
val a5 = h[2] * h[3] - h[0] * h[5]
val a6 = h[3] * h[7] - h[4] * h[6]
val a7 = h[1] * h[6] - h[0] * h[7]
val a8 = h[0] * h[4] - h[1] * h[3]
val w = a6 * point.x + a7 * point.y + a8
if (abs(w) < SINGULAR) return null
return Offset(
(a0 * point.x + a1 * point.y + a2) / w,
(a3 * point.x + a4 * point.y + a5) / w,
)
}The inverse comes from the matrix's adjugate rather than a general solver: nine products, one division, and a null when the page is edge-on and there is no point to map to.
Outcome
compose-lab is MIT-licensed and public, with a tagged first release. CI on every pull request runs Spotless, the common tests on an iOS simulator, Konsist, the Android build, lint that fails on any error, and an Xcode build of the iOS app — then a similar chain for Moodboard. More than seventeen hundred tests guard it, from the bookshelf's geometry to Compose UI tests of the components. Accessibility is designed in rather than bolted on: the bookshelf, for one, shortens and steadies its motion under reduced motion and gives screen readers a labelled spine per book and Next page, Previous page, and Close actions on an open one. It came together in under four weeks and six hundred and thirty-seven commits, all but two of them mine — the other two are Dependabot's.