#Compose Toolkit
Compose
is a modern user-interface framework. It was originally designed by Google for Android development, and released as JetPack Compose. Kotlin, Gradle, and Jetpack Compose together represent Google's preferred toolchain for Android development.
JetBrains recently ported Jetpack Compose to desktop, and released it as Compose Multiplatform. The complete toolkit has been ported to desktop, and augmented with specific desktop composables (e.g., a window
doesn't exist on Android, but does exist on desktop).
The long-term vision of Kotlin and its ecosystem is to support code-sharing across all relevant platforms, at each layer
of your application. This requires multiplatform support at each architectural layer, including multiplatform database libraries, networking libraries, user interface libraries, etc. A common GUI toolkit is a major step towards cross-platform development.
#Concepts
Compose is a declarative framework
. As compared to traditional imperative
toolkits, a declarative framework has two specific differences:
- It focuses on what you want to display on-screen, instead of how it should be created and managed. Imperative toolkits often expect the developer to use declarative syntax to describe the UI structure (e.g. Android and XML files), and then write the underlying glue-code to make that UI interactive. Declarative environments simplify this structure, so that UIs are defined completely in code. Declarative code is shorter and often simpler and easier to read.
- State-flow is handled differently. The framework itself manages state, and determines when and how to update and re-render components on-screen. This greatly simplifies the work that the developer has to do, and avoids a lot of the accidental complexity that creeps into large UIs.
A declarative structure is much simpler to read and maintain. Other modern toolkits like Swift UI and React are built around similar concepts, and have been extremely successful with this paradigm.
Compose is also cross-platform, so you can share code across platforms. This makes it easier to write a single application that can run on Android, iOS, desktop and even targets like WASM. Compose minimizes the amount of custom code that you need to write for each platform.
Finally, Compose is designed to be extremely fast. Under-the-covers, it uses skia, a high-performance Open Source graphics library, to draw on a native canvas on each target platform (the same library used by the Chrome browser). This enables it to achieve consistently high performance across all platforms, and makes it much "snappier" than early toolkits that relied on software renderers.
#Composable functions
A key concept in Compose is the idea of a composable function
, which is a particular kind of function that describes a small portion of your UI. You build your entire interface by defining a set of composable functions
that take in data and emit UI elements.
Here's an example of a simple composable function
(aka composable
) that takes in a String and displays it on-screen:
Here is the corresponding scene graph for this composable function:

Here are some characteristics of a composable:
- The function is annotated with the
@Composable
annotation. All Composable functions that we write must have this annotation. - Composable functions will often accept parameters, which are used to format the composable before displaying it.
- This function actually creates and displays the Text composable. We say that composables
emit
UI. - The function doesn't return anything. Compose functions that emit UI do not need to return anything, because they describe the desired screen state directly.
- This function is fast, idempotent, and free of side effects.
#Composable scope
Let's use this code snippet to display some text in an application window.
In this snippet, the application function defines a Composable Scope (think of a scope as the context in which our application runs). Within that scope, we use a Window
composable to emit a window. We pass it two parameters:
- a title string that will be set for the window, and
- a lambda function that will be executed when an
onCloseRequest
event is received (i.e. when the window closes, we execute the build-inexitApplication
function).
In this case, the Window
composable calls our Greeting
composable, which emits the Text
composable, which in turn displays our text.

When Jetpack Compose runs your composables for the first time, during initial composition, it will keep track of the composables that you call to describe your UI in a Composition
. Then, when the state of your app changes, Jetpack Compose schedules a recomposition.
Recomposition is when Jetpack Compose re-executes the composables that may have changed in response to state changes, and then updates the Composition to reflect any changes. A Composition can only be produced by an initial composition and updated by recomposition. The only way to modify a Composition is through recomposition.
See Lifecycle Overview for more details.
Let's try and add some interactivity to this application. We'll display our initial string on a button. When the user presses the button, it will change the value being displayed.
Our Button
is yet-another composable. It requires us to set a parameter named onClick
, which is assigned to the function (or lambda) that will be called when the button is clicked (for those familiar with other toolkits, we're assigning the onClick
event handler for that button).
Let's start by just confirming that we can print something to the console when the button is pressed.

So far it works as we'd hoped! The button displays the string passed in, and our event handler prints to the console when the button is pressed. Let's try and change our event handler so that we instead change the text on the button when it's pressed.
It's a little tricky because we cannot update the parameter directly, so we create a variable currentName to store our display value and then update that in the handler.

Nothing changed? Why didn't that work?! It has to do with how Compose manages and reflects state changes.
#Recomposition
The declarative design of Compose means that it draws the screen when the application launches, and then only redraws elements when their state changes. Compose is effectively doing this:
- Drawing the initial user interface.
- Monitoring your state (aka variables) directly.
- When a change is detected in state, the portion of the UI that relies on that state is updated.
Compose redraws affected components by calling their Composable functions. This process - detecting a change, and then redrawing the UI - is called recomposition and is the main design principle behind Compose.
Why doesn't our example work? In our example above, the onClick
handler attempts to change the text property of the Button. This triggers Compose to call the Window composable, which calls the Button composable, which initializes text
to it's initial value... Not what we intended.
We have 2 fundamental challenges to address:
- Storing state such that it is observable by Compose.
- Making sure that we persist state between calls to a Composable function, so that we're not just re-initializing it each time.
#Managing state
To make the state observable, we store it in instances of a MutableState
class that Compose can directly monitor. In our example, we'll use the mutableStateOf
wrapper function to do this:
It works! Compose detects when we have clicked on the Button(onClick
), updates the text state (currentName.value
), and then recomposes the Window and its children based on this new state.

Note that since we changed the type of text
from a String
to a MutableState<String>
, we had to change all variable references to text.value
to retrieve the actual value from the state (since it is a class with more properties than just it's state value).
We also added the remember { }
keyword to ensure that the function remembers the state values from the last time it executed. This prevents the function from reinitializing state when it recomposes.
There are multiple classes to handle different types of State. Here's a partial list—see the Compose documentation for an exhaustive list.
We can use these other types of state in appropriate places in our code. For instance, we can add WindowState to the Window and use that to set the window size and position.
This is a much more reasonable size!
#State hoisting
A composable that uses remember
is storing the internal state within that composable, making it stateful (e.g. our Greeting
composable function above).
However, storing state in a function can make it difficult to test and reuse. It's sometimes helpful to pull state out of a function into a higher-level, calling function. This process is called state hoisting.
Here's an example from the JetPack Compose documentation. In the example below, our state is the name that the user is typing in the OutlinedTextField
. Instead of storing that in our HelloContent
composable, we keep our state variable in the calling class HelloScreen
and pass in the callback function that will set that value. This allows us to reuse HelloContent by calling it from other composable functions, and keeping the state in the calling function in each case.

#Layout
Let's discuss using various @Composables
that we can use to build our user interface!
For a detailed guide on layouts, refer to Compose Layouts.
There are three basic Composables that we can use to structure our UIs:
- Column, used to arrange widget elements vertically
- Row, used to arrange widget elements horizontally
- Box, used to arrange objects in layers
#Column
A column
is a vertical arrangement of composables.

#Row
A row
is a horizontal arrangement of composables.

#Box
A box
is just a rectangular region. Use the composable alignment properties to place each of a Box's children within its boundaries.

We often nest these layout composables together:

Here's the code that builds this screen. It contains a Column as the top-level composable, and a Row at the bottom that contains Text and Button composables (which is how we have the layout flowing both top-bottom and left-right).
#Lazy Layouts
Columns and rows work fine for a small amount of data that fits on the screen. What do you do if you have large lists that might be longer or wider than the space that you have available? Ideally, we would like that content to be presented in a fixed region of the screen, and be scrollable - so that you can move up and down through the list. For performance reasons, we also want large amounts of data to be lazy loaded: only the data that is being displayed needs to be in-memory and other data is loaded only when it needs to be displayed.
Compose has a series of lazy components that work like this:
- LazyColumn
- LazyRow
- LazyVerticalGrid
- LazyHorizontalGrid
Here's an example of using a LazyRow
to present contents that are spread out horizontally, and will lazy-load.

We can do something similar to show a scrollable grid of data:

#Properties
Each class has its own parameters that can be supplied to affect its appearance and behaviour.
#Arrangement
Arrangement specifies how elements are laid out by the class. e.g. Row has a horizontalArrangement since it lays elements out horizontally; Column has a verticalArrangement to control vertical layout. Arrangement must be one of these values:
- Arrangement.SpaceEvenly: Place children such that they are spaced evenly across the main axis,
- Arrangement.SpaceBetween: Place children such that they are spaced evenly across the main axis, without free space before the first child or after the last child.
- Arrangement.SpaceAround: Place children such that they are spaced evenly across the main axis, including free space before the first child and after the last child, but half the amount of space existing otherwise between two consecutive children.
- Arrangement.Center: Place children such that they are as close as possible to the middle of the main axis.
- Arrangement.Top: Place children vertically such that they are as close as possible to the top of the main axis
- Arrangement.Bottom: Place children vertically such that they are as close as possible to the bottom of the main axis
#Alignment
Alignment specifies how elements are aligned along the dimension of this container class. e.g. top, bottom, or center. These are orthogonal to arrangement (e.g. a Row lays out elements in a horizontal path/arrangement but aligns elements vertically in that path). Alignment must be one of these values:
- Alignment.CenterHorizontally: Center across the horizontalAlignment (e.g. Column)
- Alignment.CenterVertically: Center across the verticalAlignment (e.g. Row).
#Modifier
Modifier is a class that contains parameters that are commonly used across elements. This allows us to set a number of parameters within an instance of Modifier, and pass those options between functions in a hierarchy. This is very helpful when you want to set a value and have it cascade through the scene graph (e.g. set horizontalAlignment = Alignment.CenterHorizontally
once and have it propagate).
You can see how this is used in the CombinedDemo below:
- an initial modifier is passed as a parameter to the CombinedDemo composable function. If one is not provided, it uses the default Modifier.
fun CombinedDemo(modifier:Modifier = Modifier)
- the Column composable uses the instance of the Modifier, and appends some new values to it. Column then inherits any values that were already set plus any new values that are initialized. In this case, we add
padding(16)
to the column's instance.modifier = modifier.fillMaxSize().padding(16.dp)
- Further composables that the Column calls can either use the modifier that is passed in (
modifier = modifier
) or add additional values (modifier = modifier.width(600.dp)
)
#Standard Composables
There's a very large number of widgets that you can use in Compose! Because it's cross-platform, most composables and functions exist across all supported platforms.
The Jetpack Compose reference guide is the best source of information on the Composables that are included in the material theme.
Some of the code snippets below inspired by this article: Widgets in JetPack Compose. Others are taken from the Jetpack Compose Material Components list.
#Text
A Text composable displays text.

#Image
An image composable displays an image (by default, from your Resources
folder in your project).

There are three main types of Buttons:
- Button: A standard button with no caption. Used for primary input.
- OutlinedButton: A button with an outline. Intended to be used for secondary input (lesser importance).
- TextButton: A button with a caption.
The onClick
function is called when the user pressed the button.
There are also OutlinedButton and TextButton composables

#Card
The Card
composable is a container for parts of your user-interface, intended to hold related content. e.g. a tweet, an email message, a new story in a new application and so on. It's intended to be a smaller UI element in some larger container like a Column or Row.
Here's an example from the Card composable documentation.
An elevated card populated with text and icons.
#Chip
A chip compact, interactive UI element, often with both an icon and text label. These often represent selectable or boolean values.
Here's an example from the Chip composable documentation.
#Checkbox
A checkbox is a toggleable control that presents true/false state. The OnCheckedChange
function is called when the user interacts with it (and in this case, the state represented by it
is stored in a MutableState variable named isChecked
).

#Switch
A Switch is a toggle control similar to a checkbox, in that it represents a boolean state.

#Slider
A slider lets the user make a selection from a continuous range of values. It's useful for things like adjusting volume or brightness, or choosing from a wide range of values.
Here's an example from the Slider compose documentation.

#Spacer
A spacer just adds empty space. It's useful when you only want to force space between elements.
#Scaffold
A scaffold makes it easy to build an Android-style application, with a top application bar, a bottom application bar, and elements like floating action buttons. Think of it as a pre-defined layout to help you get started with a commonly used structure.

See the App Bar documentation for examples of how to customize top and bottom app bars further.
#Using Themes
A theme is a common look-and-feel that is used when building software. Google includes their Material Design theme in Compose, and by default, composables will be drawn using the Material look-and-feel. This includes colors, opacity, shadowing and other visual elements. Apps built using the Material design system have very specific look-and-feel (example below from the Material Getting-Started documentation):

You might want to change the appearance of your application, either for product branding purposes, or to make it appear more "standard" for a specific platform. This can be done quite easily, by either extending and modifying the built-in theme, or replacing it completely.
#Customizing
To customize the default theme, we can just extend it and change its properties, and then set our application to use the modified theme. See The Color System for details on how colors are applied to Composables.

#Third-party themes
There are third-party themes that you can include to replace the Material theme completely:
- Aurora library allows you to style Compose applications using the Ephemeral design theme.
- JetBrains Jewel changes the look-and-feel to match IntelliJ applications. e.g. IntelliJ IDEA.
- MacOS theme mimics the standard macOS-look-and-feel.
Exporting from Figma
If you use Figma to build prototype user-interfaces, you can export the Compose code from Figma into your Composables directly!

In this example, we have a Text field selected. Click on Dev
mode on the right-hand toolbar, and you can see the Compose code displayed.