
Elm-architecture-driven GUI toolkit offering pure functional state management, immutable models, type-safe message handling, commands/subscriptions for side effects, and declarative UI built on Compose.
= iced-kt: A Multiplatform GUI Library :toc: left :toclevels: 3 :source-highlighter: highlightjs :icons: font
A Kotlin Multiplatform GUI library following the Elm Architecture, inspired by https://github.com/iced-rs/iced[iced-rs] and built on top of Jetpack Compose.
== Overview
iced-kt is a cross-platform GUI library that brings the Elm Architecture pattern to Kotlin Multiplatform. It provides a simple, predictable way to build interactive applications with a focus on:
== Supported Platforms
This template supports the following platforms:
[cols="1,2", options="header"] |=== |Platform |Description |JVM |Java Virtual Machine applications, including Compose Desktop |Android |Android applications and libraries |Native |iOS applications |Web |JavaScript & WebAssembly applications |===
== The Elm Architecture
iced-kt follows the Elm Architecture pattern, which consists of three core concepts:
=== Model The state of your application. This should be a simple data structure that represents everything your UI needs to display.
=== Message Events that can occur in your application (user clicks, network responses, etc.). Messages drive state changes.
=== Update A pure function that takes the current model and a message, and returns a new model. This is where your application logic lives.
=== View A function that takes the current model and returns a description of what should be displayed. The view is automatically updated when the model changes.
== Features
=== Elm Architecture Implementation Complete implementation of the Elm Architecture pattern for Kotlin Multiplatform:
=== Multiplatform Support Full Kotlin Multiplatform setup with support for:
=== Development Tools
[cols="1,1,2", options="header"] |=== |Tool |Version |Purpose |Kotlinter |Latest |Kotlin linting and code formatting |Dokka |Latest |API documentation generation |Kermit |Latest |Multiplatform logging library |===
== Getting Started
=== Prerequisites
=== Installation
Add iced-kt to your project dependencies:
=== Quick Start
Here's a simple counter application to get you started:
import xyz.malefic.compose.iced.* import androidx.compose.ui.window.Window import androidx.compose.ui.window.application
// Define your model data class Counter(val value: Int = 0)
// Define your messages sealed class CounterMessage { object Increment : CounterMessage() object Decrement : CounterMessage() }
// Create your application class CounterApp : Application<Counter, CounterMessage> { override fun init(): Counter = Counter()
override fun update(model: Counter, message: CounterMessage): Counter =
when (message) {
is CounterMessage.Increment -> model.copy(value = model.value + 1)
is CounterMessage.Decrement -> model.copy(value = model.value - 1)
}
override fun view(model: Counter, dispatch: (CounterMessage) -> Unit): Element =
Column(
children = listOf(
Text("Count: ${model.value}"),
Button("Increment", onClick = { dispatch(CounterMessage.Increment) }),
Button("Decrement", onClick = { dispatch(CounterMessage.Decrement) })
)
)
}
NOTE: The runIced() extension function creates an IcedRuntime that manages the application state and automatically handles message dispatching. When users interact with UI elements (clicking buttons, typing in text fields, etc.), the runtime calls the update function with the appropriate message and re-renders the view with the new model.
=== Message Dispatching Pattern
The view method receives a dispatch function that you use to send messages when UI events occur:
The IcedRuntime handles the entire update cycle:
update(currentModel, message) to get the new modelview(newModel) to get the new UI description=== Complete Example: Todo List Application
Here's a more complete example showing multiple component types and message handling:
import xyz.malefic.compose.iced.* import androidx.compose.ui.window.Window import androidx.compose.ui.window.application
// Model data class TodoModel( val todos: List = emptyList(), val input: String = "", val showCompleted: Boolean = true )
// Messages sealed class TodoMessage { data class UpdateInput(val text: String) : TodoMessage() object AddTodo : TodoMessage() data class RemoveTodo(val index: Int) : TodoMessage() data class ToggleShowCompleted(val show: Boolean) : TodoMessage() }
// Application class TodoApp : Application<TodoModel, TodoMessage> { override fun init() = TodoModel()
override fun update(model: TodoModel, message: TodoMessage): TodoModel =
when (message) {
is TodoMessage.UpdateInput ->
model.copy(input = message.text)
is TodoMessage.AddTodo ->
if (model.input.isNotBlank()) {
model.copy(
todos = model.todos + model.input,
input = ""
)
} else model
is TodoMessage.RemoveTodo ->
model.copy(
todos = model.todos.filterIndexed { i, _ -> i != message.index }
)
is TodoMessage.ToggleShowCompleted ->
model.copy(showCompleted = message.show)
}
override fun view(model: TodoModel, dispatch: (TodoMessage) -> Unit): Element =
Card(
children = listOf(
Text("Todo List Application"),
Spacer(size = 16),
// Input section
Row(
children = listOf(
TextField(
value = model.input,
onValueChange = { dispatch(TodoMessage.UpdateInput(it)) },
placeholder = "Enter a new todo"
),
Button("Add", onClick = { dispatch(TodoMessage.AddTodo) })
)
),
// Settings
Switch(
checked = model.showCompleted,
onCheckedChange = { dispatch(TodoMessage.ToggleShowCompleted(it)) },
label = "Show completed items"
),
Spacer(size = 8),
Text("${model.todos.size} items"),
// Todo list
Column(
children = model.todos.mapIndexed { index, todo ->
Row(
children = listOf(
Text(todo),
Button("Remove", onClick = {
dispatch(TodoMessage.RemoveTodo(index))
})
)
)
}
)
)
)
}
=== Building
To build the library for all platforms:
To run tests on all platforms:
To generate documentation:
== Publishing to Maven Central
This template uses a similar packaging secret method as other Malefic projects, making it easy to set up automated publishing.
For publishing to work, the following GitHub secrets must be configured in your repository:
GPG_KEY_ID: The ID of your GPG keyGPG_PASSPHRASE: The passphrase for your GPG keyGPG_PRIVATE_KEY: Your GPG private keySONATYPE_TOKEN_XML: Your Sonatype Central Portal user token in XML format
======= Setting Up Secrets
==== GPG Key Setup
==== Sonatype Setup
io.github.yourusername)=== Publishing Process
The template includes a GitHub Actions workflow that automatically publishes releases:
To publish manually:
== Project Structure
== Configuration
All project configuration is centralized in gradle.properties:
user=YourGitHubUsername dev=Your Full Name mail=your.email@example.com devURL=https://your-website.com
repo=YourRepositoryName g=your.group.id artifact=your-artifact-name desc=Your library description inception=2025
== Core Concepts
=== Application Interface
The Application interface is the heart of iced-kt:
=== Commands
Commands handle side effects like HTTP requests or timers:
=== Subscriptions
Subscriptions listen to external events:
=== Elements
iced-kt provides a rich set of UI elements built on top of Jetpack Compose Material3:
// Text element Text(content = "Hello, World!")
// Vertical container with children Column( children = listOf( Text("Item 1"), Text("Item 2"), Button("Click me", onClick = { /* onClick handler */ }) ) )
// Horizontal container with children Row( children = listOf( Text("Left"), Spacer(size = 16), Text("Right") ) )
// Text input field TextField( value = text, onValueChange = { newText -> /* update model */ }, placeholder = "Enter text here" )
// Checkbox with label Checkbox( checked = isChecked, onCheckedChange = { checked -> /* update model */ }, label = "Accept terms" )
// Switch/Toggle with label Switch( checked = isEnabled, onCheckedChange = { enabled -> /* update model */ }, label = "Enable feature" )
// Card container with elevation Card( children = listOf( Text("Card Title"), Text("Card content") ) )
=== Running Applications
To run an iced-kt application with Jetpack Compose, use the runIced() extension function:
import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import xyz.malefic.compose.iced.*
== License
This template is licensed under the MIT License. Update the LICENSE file with your chosen license.
== Contributing
== Architecture Benefits
=== Predictability
Since update is a pure function, the same model and message always produce the same result. This makes testing trivial and debugging much easier.
=== Testability Pure functions are easy to test. You don't need to mock anything or set up complex test environments:
=== Maintainability The strict separation between model, update, and view logic makes it clear where each piece of functionality belongs.
== Resources
= iced-kt: A Multiplatform GUI Library :toc: left :toclevels: 3 :source-highlighter: highlightjs :icons: font
A Kotlin Multiplatform GUI library following the Elm Architecture, inspired by https://github.com/iced-rs/iced[iced-rs] and built on top of Jetpack Compose.
== Overview
iced-kt is a cross-platform GUI library that brings the Elm Architecture pattern to Kotlin Multiplatform. It provides a simple, predictable way to build interactive applications with a focus on:
== Supported Platforms
This template supports the following platforms:
[cols="1,2", options="header"] |=== |Platform |Description |JVM |Java Virtual Machine applications, including Compose Desktop |Android |Android applications and libraries |Native |iOS applications |Web |JavaScript & WebAssembly applications |===
== The Elm Architecture
iced-kt follows the Elm Architecture pattern, which consists of three core concepts:
=== Model The state of your application. This should be a simple data structure that represents everything your UI needs to display.
=== Message Events that can occur in your application (user clicks, network responses, etc.). Messages drive state changes.
=== Update A pure function that takes the current model and a message, and returns a new model. This is where your application logic lives.
=== View A function that takes the current model and returns a description of what should be displayed. The view is automatically updated when the model changes.
== Features
=== Elm Architecture Implementation Complete implementation of the Elm Architecture pattern for Kotlin Multiplatform:
=== Multiplatform Support Full Kotlin Multiplatform setup with support for:
=== Development Tools
[cols="1,1,2", options="header"] |=== |Tool |Version |Purpose |Kotlinter |Latest |Kotlin linting and code formatting |Dokka |Latest |API documentation generation |Kermit |Latest |Multiplatform logging library |===
== Getting Started
=== Prerequisites
=== Installation
Add iced-kt to your project dependencies:
=== Quick Start
Here's a simple counter application to get you started:
import xyz.malefic.compose.iced.* import androidx.compose.ui.window.Window import androidx.compose.ui.window.application
// Define your model data class Counter(val value: Int = 0)
// Define your messages sealed class CounterMessage { object Increment : CounterMessage() object Decrement : CounterMessage() }
// Create your application class CounterApp : Application<Counter, CounterMessage> { override fun init(): Counter = Counter()
override fun update(model: Counter, message: CounterMessage): Counter =
when (message) {
is CounterMessage.Increment -> model.copy(value = model.value + 1)
is CounterMessage.Decrement -> model.copy(value = model.value - 1)
}
override fun view(model: Counter, dispatch: (CounterMessage) -> Unit): Element =
Column(
children = listOf(
Text("Count: ${model.value}"),
Button("Increment", onClick = { dispatch(CounterMessage.Increment) }),
Button("Decrement", onClick = { dispatch(CounterMessage.Decrement) })
)
)
}
NOTE: The runIced() extension function creates an IcedRuntime that manages the application state and automatically handles message dispatching. When users interact with UI elements (clicking buttons, typing in text fields, etc.), the runtime calls the update function with the appropriate message and re-renders the view with the new model.
=== Message Dispatching Pattern
The view method receives a dispatch function that you use to send messages when UI events occur:
The IcedRuntime handles the entire update cycle:
update(currentModel, message) to get the new modelview(newModel) to get the new UI description=== Complete Example: Todo List Application
Here's a more complete example showing multiple component types and message handling:
import xyz.malefic.compose.iced.* import androidx.compose.ui.window.Window import androidx.compose.ui.window.application
// Model data class TodoModel( val todos: List = emptyList(), val input: String = "", val showCompleted: Boolean = true )
// Messages sealed class TodoMessage { data class UpdateInput(val text: String) : TodoMessage() object AddTodo : TodoMessage() data class RemoveTodo(val index: Int) : TodoMessage() data class ToggleShowCompleted(val show: Boolean) : TodoMessage() }
// Application class TodoApp : Application<TodoModel, TodoMessage> { override fun init() = TodoModel()
override fun update(model: TodoModel, message: TodoMessage): TodoModel =
when (message) {
is TodoMessage.UpdateInput ->
model.copy(input = message.text)
is TodoMessage.AddTodo ->
if (model.input.isNotBlank()) {
model.copy(
todos = model.todos + model.input,
input = ""
)
} else model
is TodoMessage.RemoveTodo ->
model.copy(
todos = model.todos.filterIndexed { i, _ -> i != message.index }
)
is TodoMessage.ToggleShowCompleted ->
model.copy(showCompleted = message.show)
}
override fun view(model: TodoModel, dispatch: (TodoMessage) -> Unit): Element =
Card(
children = listOf(
Text("Todo List Application"),
Spacer(size = 16),
// Input section
Row(
children = listOf(
TextField(
value = model.input,
onValueChange = { dispatch(TodoMessage.UpdateInput(it)) },
placeholder = "Enter a new todo"
),
Button("Add", onClick = { dispatch(TodoMessage.AddTodo) })
)
),
// Settings
Switch(
checked = model.showCompleted,
onCheckedChange = { dispatch(TodoMessage.ToggleShowCompleted(it)) },
label = "Show completed items"
),
Spacer(size = 8),
Text("${model.todos.size} items"),
// Todo list
Column(
children = model.todos.mapIndexed { index, todo ->
Row(
children = listOf(
Text(todo),
Button("Remove", onClick = {
dispatch(TodoMessage.RemoveTodo(index))
})
)
)
}
)
)
)
}
=== Building
To build the library for all platforms:
To run tests on all platforms:
To generate documentation:
== Publishing to Maven Central
This template uses a similar packaging secret method as other Malefic projects, making it easy to set up automated publishing.
For publishing to work, the following GitHub secrets must be configured in your repository:
GPG_KEY_ID: The ID of your GPG keyGPG_PASSPHRASE: The passphrase for your GPG keyGPG_PRIVATE_KEY: Your GPG private keySONATYPE_TOKEN_XML: Your Sonatype Central Portal user token in XML format
======= Setting Up Secrets
==== GPG Key Setup
==== Sonatype Setup
io.github.yourusername)=== Publishing Process
The template includes a GitHub Actions workflow that automatically publishes releases:
To publish manually:
== Project Structure
== Configuration
All project configuration is centralized in gradle.properties:
user=YourGitHubUsername dev=Your Full Name mail=your.email@example.com devURL=https://your-website.com
repo=YourRepositoryName g=your.group.id artifact=your-artifact-name desc=Your library description inception=2025
== Core Concepts
=== Application Interface
The Application interface is the heart of iced-kt:
=== Commands
Commands handle side effects like HTTP requests or timers:
=== Subscriptions
Subscriptions listen to external events:
=== Elements
iced-kt provides a rich set of UI elements built on top of Jetpack Compose Material3:
// Text element Text(content = "Hello, World!")
// Vertical container with children Column( children = listOf( Text("Item 1"), Text("Item 2"), Button("Click me", onClick = { /* onClick handler */ }) ) )
// Horizontal container with children Row( children = listOf( Text("Left"), Spacer(size = 16), Text("Right") ) )
// Text input field TextField( value = text, onValueChange = { newText -> /* update model */ }, placeholder = "Enter text here" )
// Checkbox with label Checkbox( checked = isChecked, onCheckedChange = { checked -> /* update model */ }, label = "Accept terms" )
// Switch/Toggle with label Switch( checked = isEnabled, onCheckedChange = { enabled -> /* update model */ }, label = "Enable feature" )
// Card container with elevation Card( children = listOf( Text("Card Title"), Text("Card content") ) )
=== Running Applications
To run an iced-kt application with Jetpack Compose, use the runIced() extension function:
import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import xyz.malefic.compose.iced.*
== License
This template is licensed under the MIT License. Update the LICENSE file with your chosen license.
== Contributing
== Architecture Benefits
=== Predictability
Since update is a pure function, the same model and message always produce the same result. This makes testing trivial and debugging much easier.
=== Testability Pure functions are easy to test. You don't need to mock anything or set up complex test environments:
=== Maintainability The strict separation between model, update, and view logic makes it clear where each piece of functionality belongs.
== Resources