Skiko: the layer that lets JetBrains render one UI on JVM, Native, JS and Wasm
Kotlin Multiplatform bindings to Skia
At a glance
- What is it?
- Kotlin Multiplatform bindings to Skia, plus the per-target artifact resolution and windowing glue that any cross-platform UI needs and nobody wants to write.
- Who is it for?
- Skiko exists because drawing text and shapes at 60 frames per second across five Kotlin targets is a solved problem in C++ and an unsolved one in Kotlin. What the library provides is a substantial slice of the Skia API, a rendering context per platform, and a windowing layer that behaves consistently enough to build a real product on.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Skiko wraps and which targets it covers
Skiko, short for Skia for Kotlin, is described as a graphical library exposing a significant part of the Skia API to Kotlin along with the gluing code for a rendering context. The second half of that sentence is the important one. Skia is already cross-platform and already fast; what it lacks is a usable binding layer, and Skiko is that layer.
The supported target list is long and specific: Kotlin/JVM on Linux for x86_64 and arm64, on Windows for x86_64, and on macOS for both architectures; Kotlin/JVM on Android for x86_64 and arm64 starting from API level 24; Kotlin/JS with WebAssembly in browsers; and Kotlin/Native on iOS and macOS for both arm64 and x64.
That list is the practical answer to why this library exists. The same Kotlin source can render through AWT on the desktop, through Android's own view system on mobile, through a WebAssembly build in the browser, and through Metal or UIKit on Apple platforms. API documentation is autogenerated and published at jetbrains.github.io/skiko, and the project carries an official JetBrains project badge, so this is infrastructure rather than an experiment. It is Apache 2.0 licensed, with 2,191 stars, 205 forks and 24 open issues, last pushed 2026-09-28.
Resolving the dependency per platform
Because each target has its own native build of Skia, the artifact you depend on depends on your operating system and architecture, and Skiko's README shows you how to compute it. The Gradle snippet inspects two system properties, maps the OS name to a short key and the architecture to another, then composes them into the coordinate:
val osName = System.getProperty("os.name")
val osArch = System.getProperty("os.arch")
val target = "${targetOs}-${targetArch}"
dependencies {
implementation("org.jetbrains.skiko:skiko-awt-runtime-$target:$version")
}The mapping is a when expression over `os.name` producing `macos`, `windows` or `linux`, with an `error` branch for anything else, and a second when over `os.arch` mapping `x86_64` and `amd64` to `x64` and `aarch64` to `arm64`.
There is a real trap here, and the snippet is where it shows. The version line in that example reads `0.8.9` with a comment saying any more recent version is fine, while the published releases are numbered in the 0.150 range, with v0.153.0 released on 2026-09-23. The coordinates also point at `skiko-awt-runtime`, which is the desktop AWT flavor; the repository publishes per-platform variants for the other targets as well. Treat the snippet as a shape to copy rather than a line to paste, and take the version from the release page rather than from the README.
The rendering delegate pattern in practice
The JVM example shows the abstraction that keeps the code portable. You create a `SkiaLayer`, attach a `SkiaLayerRenderDelegate`, and inside it supply an object implementing `SkikoRenderDelegate` with a single `onRender` method that receives a `Canvas`, a width, a height and a nano timestamp. The delegate clears the canvas and draws a moving circle:
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
canvas.clear(Color.CYAN)
val ts = nanoTime / 5_000_000
canvas.drawCircle( (ts % width).toFloat(), (ts % height).toFloat(), 20f, paint )
}The same delegate body appears unchanged in the iOS example, which is the whole point: the drawing code does not know whether it is running on a desktop JVM or an iPhone. What changes is the surrounding windowing. On the JVM, the layer attaches to a Swing `JFrame` content pane inside `SwingUtilities.invokeLater`, the window is packed and made visible. On iOS, the equivalent involves a `UIApplicationDelegate` subclass with an explicit `constructor() : super()` annotated for Objective-C interop, a `UIWindow` sized to `UIScreen.mainScreen.bounds`, a root `UIViewController` wrapping a `SkikoUIView` that holds the layer, and the whole entry point running inside `memScoped` with an `autoreleasepool` around `UIApplicationMain`.
One honest observation about the samples: in the iOS snippet, the layer is created inline as `SkiaLayer().apply { ... }` while the delegate constructor still refers to a `skiaLayer` identifier that is not in scope in that block. The `samples/SkiaMultiplatformSample` directory that the README points to is the place to check the current form of this code rather than treating the README listing as a compiling template.
Version cadence tied to Skia milestones
The release tags mirror Skia's own milestone numbering, which tells you where the project's centre of gravity is. v0.153.0 updated Skia to m153, v0.152.0 updated to m152, and v0.150.2 updated to a specific m150 build identified by commit hash rather than a plain milestone. Your Skiko version is therefore a proxy for which Skia you get, and Skia's own release cadence is what sets your upgrade rhythm.
The non-Skia changes in those releases are a good sample of what maintaining a graphics binding actually involves. v0.152.0 made window resizing smooth on Windows with Direct3D, eliminated a background flash on the first window show, disposed the Metal surface immediately after submitting, fixed `equals`, `hashCode` and `copy` on `SkiaLayerProperties`, collapsed the per-backend render stack in AWT, exposed `Canvas.drawAnnotation`, and extracted the wasm-opt phase into a separate build step. v0.153.0 added a benchmarks module, made `RenderNode.setBounds` skip content invalidation for topLeft-only changes, built the wasm artifact with size optimisation, and avoided duplicated object files across Skia static archives for iOS and tvOS.
Those are unglamorous fixes and they are the ones that determine whether a UI feels solid, which is the argument for bindings being maintained by whoever ships a commercial IDE rather than assembled by individual users.
One irregularity in the timeline is worth noting rather than smoothing over. v0.150.2 was published on 2026-09-28, five days after v0.153.0 on 2026-09-23. A patch on an older line shipping after a newer minor usually means a backport, so picking the highest tag number is not always the same as picking the most recent artifact. Check the dates on the releases page if you are pinning.
Repository layout and where to start
The tree is small for a project of this size, which suggests the bulk of the code lives under a single module. Alongside the `skiko/` directory there is a `samples/` directory holding five runnable examples: `SkiaAndroidSample`, `SkiaAwtSample`, `SkiaMultiplatformSample`, `SkiaWebSample` and `SkikoExtensionsSample`. Between them they cover every platform on the support list plus a sample for the higher-level extensions, and each is a better starting point than a README snippet because it compiles.
Build infrastructure is standard Kotlin multiplatform: `gradlew` and `gradlew.bat`, `settings.gradle.kts`, `gradle.properties` and a `dependencies.toml` that pins the Skia version in one place rather than scattering it through the build. A `benchmarks/` directory at the root matches the benchmarks module added in v0.153.0, and `DEVELOPMENT.md` covers building the project itself. There is a `SECURITY.md` and a `CODE_OF_CONDUCT.md`, and a `NOTICE` file alongside the Apache 2.0 `LICENSE`, which is the file Apache licensing requires for third-party attributions.
The default branch is `master` rather than `main`, which is worth knowing before you file a pull request, and the contribution, security and code of conduct files sit alongside the build scripts. If you are evaluating this library, the fastest route is to pick the sample matching your target, confirm the coordinate resolves for your OS and architecture, and check the newest release's Skia milestone against whatever version of Skia the rest of your stack expects.
Editorial conclusion
Skiko exists because drawing text and shapes at 60 frames per second across five Kotlin targets is a solved problem in C++ and an unsolved one in Kotlin. What the library provides is a substantial slice of the Skia API, a rendering context per platform, and a windowing layer that behaves consistently enough to build a real product on. The cost is a native library per target, a dependency coordinate that must be resolved per operating system and architecture, and a version cadence tied to Skia itself rather than to your needs. Check the release tags before pinning, since the README's build-script example still references 0.8.9 while published versions are in the 0.150 range, and the samples directory is the fastest way to see which windowing API is current.
Frequently asked questions
What is Skiko?
Skiko is a Kotlin Multiplatform library that exposes a large part of the Skia graphics API to Kotlin, along with the rendering context and windowing glue needed to draw with it. It targets Kotlin/JVM on desktop and Android, Kotlin/Native on iOS and macOS, and Kotlin/JS with WebAssembly in browsers. It is what lets the same drawing code compile and run across all of those platforms.
Which platforms does Skiko support?
Kotlin/JVM on Linux for x86_64 and arm64, on Windows for x86_64, and on macOS for both, plus Kotlin/JVM on Android from API level 24. It also covers Kotlin/JS with WebAssembly in browsers, and Kotlin/Native on iOS and macOS for arm64 and x64. Each target has its own artifact, so you resolve the dependency per operating system and architecture.
How do I add Skiko as a Gradle dependency?
The artifact name embeds the operating system and architecture, so the usual approach is to compute them from the `os.name` and `os.arch` system properties in your build script and then depend on the matching coordinate such as `org.jetbrains.skiko:skiko-awt-runtime-linux-x64`. Take the version from the release page rather than the README, whose example still references 0.8.9 while published releases are in the 0.150 range.
How often does Skiko change?
Frequently, and the reason is upstream. Release tags track Skia milestones, so v0.152.0 and v0.153.0 correspond to Skia m152 and m153. Between the 2026-09-10 and 2026-09-23 releases the changes were largely native: Direct3D resize smoothness on Windows, removing a first-show window flash, disposing the Metal surface earlier, wasm build size, and AWT render stack consolidation.
Does Skiko include window management, or only drawing?
Both, though the windowing is deliberately thin. Drawing goes through the SkikoRenderDelegate interface with an onRender callback receiving a canvas, width, height and timestamp. Window creation is left to each platform's own conventions: a Swing JFrame on the JVM, a UIWindow and UIViewController on iOS, and the usual equivalents elsewhere.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jetbrains-skiko)