# Compose Swing UI
[](https://github.com/JetBrains#jetbrains-on-github)
[](https://github.com/JetBrains/compose-swing-ui/releases/latest)
[](https://github.com/JetBrains/compose-swing-ui/actions/workflows/snapshot.yml)
[](https://jetbrains.github.io/compose-swing-ui/)
A declarative, reactive way to build **Swing** UIs using Jetpack Compose's composition model -
built on **Compose Runtime only**. No skiko, no Compose Multiplatform UI, no Skia renderer. Your
components are real `JButton`/`JLabel`/`JPanel` widgets, laid out by Swing's own `LayoutManager`s
and painted by the platform look-and-feel; Compose drives state and composition.
Inspired by [Compose HTML](https://github.com/JetBrains/compose-multiplatform) (DOM target) and
[Mosaic](https://github.com/JakeWharton/mosaic) (terminal target).
<!--- INCLUDE .*readme.*
import androidx.compose.runtime.*
import org.jetbrains.compose.swing.components.*
import org.jetbrains.compose.swing.components.button.*
import org.jetbrains.compose.swing.components.layout.*
import org.jetbrains.compose.swing.foundation.layout.*
import org.jetbrains.compose.swing.components.menu.*
import org.jetbrains.compose.swing.modifier.*
import org.jetbrains.compose.swing.modifier.appearance.*
import org.jetbrains.compose.swing.modifier.interaction.*
import org.jetbrains.compose.swing.setContent
import org.jetbrains.compose.swing.window.*
import java.awt.Color
import java.awt.Dimension
import javax.swing.JFrame
import javax.swing.SwingConstants
import javax.swing.SwingUtilities
-->
## Quick start
A minimal app using the `application` entry point, a `Window`, a couple of components, and the
`BorderLayout` regions a `Panel` under `PanelLayout.Border` offers:
```kotlin
fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "Counter") {
var count by remember { mutableIntStateOf(0) }
Panel(PanelLayout.Border()) {
Label(
"Compose Swing UI",
modifier = SwingModifier.north().horizontalAlignment(SwingConstants.CENTER),
)
Panel {
Label("Count: $count")
Button("Increment", onClick = { count++ })
Button("Decrement", onClick = { count-- })
}
Label(
"Status: ready",
modifier = SwingModifier.south().horizontalAlignment(SwingConstants.CENTER),
)
}
}
}
```
<!--- KNIT example-readme-01.kt -->
A child of a `PanelLayout.Border` panel names its `BorderLayout` region on its own modifier:
`north()`, `south()`, `center()`, and the rest of `BorderLayout`'s. A child that names no region
occupies the center, so the panel's main content is written plainly.
Every component family the library ships - text inputs, buttons, selection, layout containers,
windows, dialogs and menus - is cataloged with the parameters that decide how it behaves in
[`docs/COMPONENTS.md`](docs/COMPONENTS.md). Every public declaration is documented in the
[API reference](https://jetbrains.github.io/compose-swing-ui/), which tracks `master`.
## Mounting into existing Swing (`setContent`)
You can also drive composition into any container without the `application`/`Window` entry points.
`setContent` is an extension on `javax.swing.JComponent`, with `java.awt.Window.setContent` (covering
`JFrame`, `JDialog`, and `JWindow`) and `JMenuBar.setContent` provided too. A single
`import org.jetbrains.compose.swing.setContent` resolves all of them:
```kotlin
fun main() {
SwingUtilities.invokeLater {
val frame = JFrame("My App")
frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
frame.size = Dimension(600, 400)
frame.setContent {
var count by remember { mutableIntStateOf(0) }
Panel {
Label("Count: $count")
Button("Increment", onClick = { count++ })
}
}
frame.isVisible = true
}
}
```
<!--- KNIT example-readme-02.kt -->
`setContent` is called on the Event Dispatch Thread and returns a `DisposableHandle`; dispose it to
tear the composition down. Nesting works: a `setContent` whose ancestor already hosts a composition
joins that composition and shares its recomposition scope.
## Menus
`MenuBar` declares the menu bar of the window whose content it is composed in, driven by the state its
content reads. It is declared on the window scope a `Window` and a `Dialog` give their content, so a
menu bar with no window to carry it does not compile:
```kotlin
fun main() = application {
var opened by remember { mutableStateOf("nothing") }
Window(onCloseRequest = ::exitApplication, title = "Editor") {
MenuBar {
Menu("File") {
MenuItem("New", onClick = { opened = "a new file" })
MenuItem("Open", onClick = { opened = "an existing file" })
MenuSeparator()
MenuItem("Exit", onClick = ::exitApplication)
}
}
Label("Opened: $opened")
}
}
```
<!--- KNIT example-readme-03.kt -->
The same tree fills a context menu through `ContextMenu(anchor) { ... }` and a tray icon's menu
through `Tray(menu = { ... })`. On a `JMenuBar` the application builds itself, `JMenuBar.setContent { ... }`
takes it too.
## Styling & interaction with `SwingModifier`
Components take an optional `modifier: SwingModifier = SwingModifier` parameter for visual and
interaction concerns - colors, fonts, borders, tooltips, focus, hover. Build a chain with the
extension builders:
```kotlin
@Composable
fun SaveButton(onSave: () -> Unit) {
var hovered by remember { mutableStateOf(false) }
Button(
text = "Save",
onClick = onSave,
modifier =
SwingModifier
.foreground(Color.WHITE)
.lineBorder(if (hovered) Color.BLUE else Color.GRAY)
.onHover(onEnter = { hovered = true }, onExit = { hovered = false }),
)
}
```
<!--- KNIT example-readme-04.kt -->
`lineBorder` and `emptyBorder` declare a border by its values and rebuild it only when they change.
Hoist the other value objects a chain carries - a `Font` or an `Icon` - into `remember`.
Domain callbacks like `onClick` and `onValueChange` stay ordinary parameters; only cross-cutting
styling and interaction flow through `modifier`. Builders are grouped by concern, one package each:
appearance, layout, interaction, listener, keyboard, data transfer, and accessibility. See
[`docs/MODIFIERS.md`](docs/MODIFIERS.md#equality-and-skipping) for why a modifier built in place needs no
`remember`.
## Bring your own Swing component
Any Swing `Component` can be hosted directly with `SwingNode` - a first-class, supported way to bring
custom Swing components into a composition. Every built-in wrapper is built on `SwingNode` the same
way. See [`docs/CUSTOM-COMPONENTS.md`](docs/CUSTOM-COMPONENTS.md) for a step-by-step guide, and
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for how the composition drives the Swing tree.
Four further guides carry the rest:
- [`docs/COMPONENT-STATE.md`](docs/COMPONENT-STATE.md) - a property the user can change as well as
the composition, and the state holders that carry what a declared value cannot.
- [`docs/MODIFIERS.md`](docs/MODIFIERS.md) - how a modifier is ordered, matched and attached across
passes, the `modifier` parameter, writing a property element of your own, and attaching listeners.
- [`docs/FOUNDATION.md`](docs/FOUNDATION.md) - constraint-based layout, drawing and decorations.
- [`docs/CUSTOM-CONTAINERS.md`](docs/CUSTOM-CONTAINERS.md) - containers, the placements they offer
their children, and rendering items with a composable cell.
To ask a mounted composition what it holds for a component and where that component was declared,
see [`docs/INSPECTING-COMPOSITIONS.md`](docs/INSPECTING-COMPOSITIONS.md).
## Animation
`swing-ui-animation` provides androidx Compose's animation-core APIs - `animate*AsState`, `Animatable`,
`updateTransition`, easing curves, and the `spring` / `tween` / `keyframes` specs - for `Float`,
`Int`, `java.awt.Color`, and any type you supply a `TwoWayConverter` for. It also provides
`AnimatedVisibility`, `AnimatedContent` and `Crossfade`, which animate content as it appears, disappears
or is replaced, and `animateContentSize`, which animates a layout's size as its content changes. See
[`swing-ui-animation/README.md`](swing-ui-animation/README.md).
## Testing
Add `:swing-ui-test` and write plain `@Test` methods whose body is a `runComposeSwingTest { ... }`
block - the harness is synchronous and deterministic (off-screen, never sleeps).
See [`docs/TESTING-COMPONENTS.md`](docs/TESTING-COMPONENTS.md) for the finders, assertions, actions,
and screenshot comparison the harness offers.
## Build, run, test
```bash
./gradlew build # compile + all quality gates + tests
./gradlew :samples:todo-app:run # run the to-do sample
./gradlew :samples:widgets-gallery:run # run the widgets gallery
./gradlew test # tests only
```
Full quality-gate command (what CI runs):
```bash
./gradlew :buildSrc:ktlintCheck :buildSrc:detekt detektMain detektTest detektTestFixtures
./gradlew build
```
[`CONTRIBUTING.md`](CONTRIBUTING.md) walks through what each gate covers.
The project is split into focused artifacts: [`swing-ui`](swing-ui/README.md) supplies the runtime,
Swing wrappers, and Swing layout-manager containers; [`swing-ui-foundation`](swing-ui-foundation/README.md)
supplies drawing and constraint-based layout; [`swing-ui-animation`](swing-ui-animation/README.md)
supplies animation; [`swing-ui-test`](swing-ui-test/README.md) supplies the deterministic test harness;
and [`swing-ui-detekt`](swing-ui-detekt/README.md) supplies the project's detekt rules. The runnable
samples and documentation snippets are kept in `samples/`.
`swing-ui` and `swing-ui-foundation` split along what Swing itself can already do: `swing-ui` wraps a
Swing/AWT capability that already exists (a component, a listener, a `LayoutManager`) in a declarative
API; `swing-ui-foundation` mimics the part of Compose UI/Foundation's own API surface that Swing has no
equivalent for at all (weighted two-pass measurement, `Row`/`Column`/`Box`, draw scopes, brushes and
shapes), and had to be built from scratch to do it.
## Stability
Pre-1.0: breaking API changes may land in any minor release. Kotlin 2.2 or newer is required to
consume the libraries. Below Kotlin 2.4, calling the Foundation layout modifiers also needs
the `-Xcontext-parameters` compiler option; see [Scoped modifiers](docs/FOUNDATION.md#scoped-modifiers).
## License
Licensed under the Apache License, Version 2.0 - see [LICENSE](LICENSE).
`swing-ui` redistributes source code from the Android Open Source Project's Jetpack Compose `compose.ui:ui` and
`ui-util`, `swing-ui-foundation` its `foundation`, `foundation-layout`, `compose.ui:ui`, `ui-graphics`, `ui-unit`
and `ui-util`, `swing-ui-animation` its `animation-core` and `animation`, and `swing-ui-detekt` one of that
project's Android Lint checks, all under the same license. See each module's `META-INF/NOTICE`
and the per-file headers for attribution.