Skip to content

About

Kotlin Multiplatform form library for Compose Multiplatform (Android + iOS) and SwiftUI

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kotose Form KMP

A Kotlin Multiplatform form library. You define a form once in shared Kotlin: fields, groups, validation, conditions and submission. It then renders natively on Android with Compose, and on iOS with either Compose Multiplatform or native SwiftUI. Kotlin keeps the state and the rules; each renderer only draws the platform controls.

Modules

Artifact Contents
io.github.robinpcrd:kotose-form-core Renderer-neutral model: Form, groups, fields, validation, conditions, navigation and the DSL
io.github.robinpcrd:kotose-form-phone Phone number field and countries; without a backend it takes a typed dial code and number
io.github.robinpcrd:kotose-form-phone-backend Optional: country picker, parsing, validation and formatting with libphonenumber on Android and PhoneNumberKit on iOS
io.github.robinpcrd:kotose-form Compose renderer (FormScreen) for Android and iOS, styled fields and the legacy Compose DSL
io.github.robinpcrd:kotose-form-swiftui Native SwiftUI renderer (KotoseNativeForm) and its Kotlin adapter (NativeFormAdapter), experimental

Features

  • Kotlin DSL for text, password, phone, toggle, date and date-time fields, single and multiple selection, read-only text and actions, plus typed custom fields.
  • Validation per field and per group, on demand or live, with warnings, silent errors and scroll to the first error.
  • Conditions that show, hide, enable or disable fields from other values; hidden values are not submitted.
  • Keyboard navigation with Next and Done across the form's inputs.
  • Presentations for selections: inline, menu, sheet, page or segmented, with an optional iOS override. iOS dates use the compact picker or keyboard-style wheels.
  • Styling through KotoseFormStyle on both renderers.

Installation

Compose on Android and iOS:

commonMain.dependencies {
    implementation("io.github.robinpcrd:kotose-form:<version>")
}

Native SwiftUI uses the SwiftUI artifact, which brings the core and phone modules but not Compose. Add it to the shared KMP module that builds the iOS framework; that module applies SKIE, builds one static framework and exports the SwiftUI artifact:

plugins { id("co.touchlab.skie") }

kotlin {
    listOf(iosArm64(), iosSimulatorArm64()).forEach { target ->
        target.binaries.framework {
            isStatic = true
            export("io.github.robinpcrd:kotose-form-swiftui:<version>")
        }
    }
    sourceSets.commonMain.dependencies {
        api("io.github.robinpcrd:kotose-form-swiftui:<version>")
    }
}

iOS setup

  • Use Kotlin 2.4.20 or later (Xcode 27 needs it), with a SKIE version that supports it.
  • The Kotlin build phase in Xcode must run embedAndSignAppleFrameworkForXcode.
  • The SwiftUI renderer's Swift sources are compiled into your framework, next to every Kotlin type it exports. Exporting kotose-form-swiftui alone is enough. A compile error in KotoseNativeForm.swift is a bug: please report it.
  • To use your own custom fields from Swift, also export the module that defines them; see Custom fields.

Only the optional phone backend needs a Swift package (PhoneNumberKit), through Kotlin's SwiftPM import, which is still Alpha. Connect it to your Xcode project once:

XCODEPROJ_PATH=/path/to/iosApp.xcodeproj ./gradlew :shared:integrateLinkagePackage
  • The task finds the Kotlin build phase by the word gradle in its script. A phase that only calls a wrapper script fails with "embedAndSign integration wasn't found"; mentioning gradle in the phase, even in a comment, fixes it.
  • It rewrites project.pbxproj in Xcode's canonical format, so expect a large formatting diff.
  • The generated package targets iOS 15. Set swiftPMDependencies { iosMinimumDeploymentTarget } for another minimum.

Phone numbers

Phone fields work without extra dependencies: the row takes a dial code, after a fixed +, and the number, as digits. For the country picker, per-country validation and formatting as the user types, add the backend and set it once at startup, before building forms:

commonMain.dependencies {
    implementation("io.github.robinpcrd:kotose-form-phone-backend:<version>")
}
PhoneNumberBackend.current = LibPhoneNumberBackend

On iOS the backend needs the Swift package step from iOS setup; apps without it skip that step. See Phone numbers for what each mode does.

Platforms

  • Android SDK 24+
  • iOS arm64 and simulator arm64; the SwiftUI renderer needs iOS 16+

Quick start

Define the form in shared Kotlin:

data class Plan(val code: Int)

val signUp = form {
    +group {
        +displayText(text = StrRes(text = "Create an account"), isTitle = true)
        +textField(tag = "name", label = StrRes(text = "Name")) { addRule(RequiredFieldRule()) }
        +secureTextField(tag = "password", label = StrRes(text = "Password"))
        +toggleField(tag = "terms", label = StrRes(text = "Accept the terms"))
    }
    +singleSelectionGroup<Plan> {
        tag = "plan"
        title = StrRes(text = "Plan")
        presentation = SelectionPresentation.Menu
        +selectionOption("basic", Plan(1), StrRes(text = "Basic"))
        +selectionOption("pro", Plan(2), StrRes(text = "Pro"))
    }
}

// On submit: an empty list means valid, and values are keyed by tag.
val errors = signUp.validate()
val values = signUp.allValues()

Render it with Compose:

@Composable
fun SignUpScreen() {
    FormScreen(form = signUp)
}

Or with SwiftUI, from a NativeFormAdapter(signUp) created in Kotlin. The adapter is experimental: opt in with @OptIn(ExperimentalKotoseSwiftUIApi::class).

struct SignUpView: View {
    let adapter: NativeFormAdapter

    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                KotoseNativeForm(adapter: adapter, scrollProxy: proxy) { _, _ in AnyView(EmptyView()) }
                    .padding(.horizontal)
            }
        }
    }
}

The closure draws custom fields; see Custom fields. A selection shown as a page needs a NavigationStack around the form.

Documentation

The example/ app shows every field on the three renderers, with a Compose and SwiftUI switch on each page; see Contributing to run it.

Contributing

See CONTRIBUTING.md for build, test, and contribution guidelines.

License

Copyright 2025-present Robin PICARD and contributors.
Licensed under the Apache License, Version 2.0.

Originally created at PandaLab.

About

Kotlin Multiplatform form library for Compose Multiplatform (Android + iOS) and SwiftUI

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages