
Spatial browser gallery for Compose @Preview annotations, discovers previews via annotation processing, renders stable, deep-linkable frames on a pan-and-zoom Wasm board, and exports optimized static galleries.
Artboard is a spatial browser gallery for Compose Multiplatform @Previews.
It discovers previews with KSP, hangs them on a pan-and-zoom board, and gives
every frame a stable URL-addressable ID.
Try the live Crowded Café demo.
@Preview annotations, including repeat previews and
both current and legacy Compose Preview packages.wasmJs): fully interactive previews on a
pan-and-zoom board with mouse, trackpad, one-finger pan, and pinch-zoom.jvm or android only): pre-rendered PNG tiles in the
same board chrome — theme, locale, device, search, deep links, and the screen
layout-grid overlay work without a Wasm target.Artboard picks one gallery mode from the targets you already declare (preference order: live Wasm → JVM snapshots → Android snapshots):
| You declare | Mode | What you get |
|---|---|---|
wasmJs { browser() } |
Live | Interactive Compose in the browser. Previews must compile for Wasm. |
jvm() (no wasmJs) |
Snapshot | Headless Skia renders every theme × locale to PNGs; a prebuilt viewer browses them. |
android { … } only |
Snapshot | Same as JVM, but Robolectric host tests render the images. Needs an Android SDK. |
wasmJs is not required. Without it you still get a spatial gallery of
images with the board chrome (pan/zoom, search, theme, locale, layout grid,
export). You do not get live interaction inside a preview, animation scrubbing,
or per-frame PNG capture of a running composition — those need the live path.
Artboard releases are published to Maven Central. Add Maven Central to plugin resolution and apply the plugin:
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
id("org.jetbrains.kotlin.plugin.compose")
id("io.github.crowded-libs.artboard") version "0.2.2"
}kotlin {
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
wasmJs { browser() }
}Use a target you already have. Previews only need to compile for that target:
// JVM snapshots — fastest local loop, no Android SDK
kotlin {
jvm()
}
// Or Android-only modules (Robolectric host tests render the tiles)
kotlin {
android {
namespace = "com.example.ui"
compileSdk = 36
minSdk = 24
}
}Use stock Compose @Preview annotations, including previews declared in
commonMain. Artboard adds its KSP processor and runtime only to the gallery
graph; it never adds targets, platform actuals, or source-level Artboard APIs
to your application.
./gradlew :ui:artboardDoctor
./gradlew :ui:artboardReport # build/reports/artboard/previews.json
./gradlew :ui:artboardRun # live or snapshot gallery, depending on targets
./gradlew :ui:artboardRunLan # same build, reachable on the local network
./gradlew :ui:artboardSnapshot # snapshot mode only: PNGs + manifest.json
./gradlew :ui:artboardExport # build/artboard/exportartboardRun serves the gallery for whichever mode you are in. In snapshot mode
it runs artboardSnapshot, unpacks Artboard's prebuilt viewer, and serves the
assembled board. artboardExport produces the optimized production site without
a long-running server. Neither task builds Android or iOS app targets for their
own sake.
The gallery light/dark control provides Compose's standard system-theme signal to each preview. Artboard deliberately does not wrap preview content in its own Material theme, so your application remains responsible for its design system.
To make a preview respond to the gallery toggle, wrap it in your normal app
theme and derive its mode from isSystemInDarkTheme():
import androidx.compose.foundation.isSystemInDarkTheme
@Preview(name = "Account")
@Composable
fun AccountPreview() {
AppTheme(darkTheme = isSystemInDarkTheme()) {
AccountScreen(state = previewState)
}
}For a live gallery the theme wrapper and its dependencies must compile for
wasmJs. For a snapshot gallery they must compile for the snapshot target
(jvm or android). A preview that hard-codes light or dark mode remains valid,
but will not react to the gallery theme control.
Inside each frame body (not the gallery chrome), Artboard sets
LocalInspectionMode to true, the same signal IDE Compose previews provide.
Code that already branches on inspection mode — sample drawables instead of
network images, placeholders, skipped side effects — behaves on the board the
way it does in Android Studio / IDEA. Gallery toolbar, board, and menus stay
outside inspection mode so the host remains a normal interactive UI.
The toolbar Grid control toggles a Figma-style column / margin / gutter overlay on every Screen frame. It is chrome painted above the frame body, so it works for live composables and for snapshot PNG tiles the same way. PNG download (live mode) still excludes the overlay so store artwork stays clean.
In the live gallery, each frame has a PNG ↓ action that captures the
preview body exactly as it is currently composed. Camera position and zoom do
not affect the image, and Artboard chrome, selection marks, and layout grids are
excluded.
Screen previews matching a built-in device viewport download at that device's native pixel size and are flattened to an opaque background. Other screens and components use a 2× logical-size fallback; component transparency is preserved. Theme, locale, interaction state, and the current animation frame are included in the capture.
In snapshot mode the board already is the image set: every theme × locale
variant was rendered by artboardSnapshot. Use the locale and theme controls to
switch tiles; per-frame download is a live-gallery feature.
Native-sized images are convenient for store artwork, but should still be checked against the Android or iOS app before submission when platform rendering details matter.
Requirements: JDK 17+, a WasmGC-capable browser for live gallery work, Android SDK for Android snapshot samples and the café Android app, and Xcode for iOS showcase work.
# Core tests, runtime Wasm, and the prebuilt snapshot viewer jar
./gradlew test :artboard-runtime:jvmTest :artboard-runtime:compileKotlinWasmJs :artboard-viewer-dist:jar
# Live Wasm consumer contract
./gradlew -p samples/minimal artboardDoctor artboardReport compileKotlinWasmJs
# JVM snapshot consumer (no wasmJs)
./gradlew -p samples/light artboardDoctor artboardReport artboardSnapshot artboardExport
# Android-only snapshot consumer (no wasmJs / jvm)
./gradlew -p samples/android-light artboardDoctor artboardReport artboardSnapshot artboardExport
# Café gallery, Android, and iOS verification
./gradlew -p showcase/cafe :shared:artboardExport
./gradlew -p showcase/cafe :androidApp:assembleDebug
./gradlew -p showcase/cafe :shared:iosSimulatorArm64Test :shared:linkDebugFrameworkIosSimulatorArm64For UI or host changes, run the relevant artboardRun, open its printed URL,
check the browser console, and exercise the changed control (including Grid
on a Screen frame in snapshot mode). Keep screenshots and verification artifacts
under /tmp, never in the repository.
The hero animation is recorded locally from the café export (not CI):
./gradlew -p showcase/cafe :shared:artboardExport # once, or when the showcase changes
cd scripts && npm install # once
node record-demo.mjs # writes ../artboard_sample.gif
# or: node record-demo.mjs --build # export + record
# or: node record-demo.mjs --url http://127.0.0.1:8080/Requires Node 20+, ffmpeg on PATH, and Google Chrome (or Playwright Chromium).
The tour drives kind filters, search, layout grid, zoom, theme, pan, frame
selection, and an Arabic locale switch on the Settings screen (via
#frame=…&locale=ar deep links — Popup menus are not scriptable under Playwright).
Artboard is licensed under Apache-2.0. Bundled font notices are in THIRD_PARTY_NOTICES.md.
Artboard is a spatial browser gallery for Compose Multiplatform @Previews.
It discovers previews with KSP, hangs them on a pan-and-zoom board, and gives
every frame a stable URL-addressable ID.
Try the live Crowded Café demo.
@Preview annotations, including repeat previews and
both current and legacy Compose Preview packages.wasmJs): fully interactive previews on a
pan-and-zoom board with mouse, trackpad, one-finger pan, and pinch-zoom.jvm or android only): pre-rendered PNG tiles in the
same board chrome — theme, locale, device, search, deep links, and the screen
layout-grid overlay work without a Wasm target.Artboard picks one gallery mode from the targets you already declare (preference order: live Wasm → JVM snapshots → Android snapshots):
| You declare | Mode | What you get |
|---|---|---|
wasmJs { browser() } |
Live | Interactive Compose in the browser. Previews must compile for Wasm. |
jvm() (no wasmJs) |
Snapshot | Headless Skia renders every theme × locale to PNGs; a prebuilt viewer browses them. |
android { … } only |
Snapshot | Same as JVM, but Robolectric host tests render the images. Needs an Android SDK. |
wasmJs is not required. Without it you still get a spatial gallery of
images with the board chrome (pan/zoom, search, theme, locale, layout grid,
export). You do not get live interaction inside a preview, animation scrubbing,
or per-frame PNG capture of a running composition — those need the live path.
Artboard releases are published to Maven Central. Add Maven Central to plugin resolution and apply the plugin:
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
id("org.jetbrains.kotlin.plugin.compose")
id("io.github.crowded-libs.artboard") version "0.2.2"
}kotlin {
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
wasmJs { browser() }
}Use a target you already have. Previews only need to compile for that target:
// JVM snapshots — fastest local loop, no Android SDK
kotlin {
jvm()
}
// Or Android-only modules (Robolectric host tests render the tiles)
kotlin {
android {
namespace = "com.example.ui"
compileSdk = 36
minSdk = 24
}
}Use stock Compose @Preview annotations, including previews declared in
commonMain. Artboard adds its KSP processor and runtime only to the gallery
graph; it never adds targets, platform actuals, or source-level Artboard APIs
to your application.
./gradlew :ui:artboardDoctor
./gradlew :ui:artboardReport # build/reports/artboard/previews.json
./gradlew :ui:artboardRun # live or snapshot gallery, depending on targets
./gradlew :ui:artboardRunLan # same build, reachable on the local network
./gradlew :ui:artboardSnapshot # snapshot mode only: PNGs + manifest.json
./gradlew :ui:artboardExport # build/artboard/exportartboardRun serves the gallery for whichever mode you are in. In snapshot mode
it runs artboardSnapshot, unpacks Artboard's prebuilt viewer, and serves the
assembled board. artboardExport produces the optimized production site without
a long-running server. Neither task builds Android or iOS app targets for their
own sake.
The gallery light/dark control provides Compose's standard system-theme signal to each preview. Artboard deliberately does not wrap preview content in its own Material theme, so your application remains responsible for its design system.
To make a preview respond to the gallery toggle, wrap it in your normal app
theme and derive its mode from isSystemInDarkTheme():
import androidx.compose.foundation.isSystemInDarkTheme
@Preview(name = "Account")
@Composable
fun AccountPreview() {
AppTheme(darkTheme = isSystemInDarkTheme()) {
AccountScreen(state = previewState)
}
}For a live gallery the theme wrapper and its dependencies must compile for
wasmJs. For a snapshot gallery they must compile for the snapshot target
(jvm or android). A preview that hard-codes light or dark mode remains valid,
but will not react to the gallery theme control.
Inside each frame body (not the gallery chrome), Artboard sets
LocalInspectionMode to true, the same signal IDE Compose previews provide.
Code that already branches on inspection mode — sample drawables instead of
network images, placeholders, skipped side effects — behaves on the board the
way it does in Android Studio / IDEA. Gallery toolbar, board, and menus stay
outside inspection mode so the host remains a normal interactive UI.
The toolbar Grid control toggles a Figma-style column / margin / gutter overlay on every Screen frame. It is chrome painted above the frame body, so it works for live composables and for snapshot PNG tiles the same way. PNG download (live mode) still excludes the overlay so store artwork stays clean.
In the live gallery, each frame has a PNG ↓ action that captures the
preview body exactly as it is currently composed. Camera position and zoom do
not affect the image, and Artboard chrome, selection marks, and layout grids are
excluded.
Screen previews matching a built-in device viewport download at that device's native pixel size and are flattened to an opaque background. Other screens and components use a 2× logical-size fallback; component transparency is preserved. Theme, locale, interaction state, and the current animation frame are included in the capture.
In snapshot mode the board already is the image set: every theme × locale
variant was rendered by artboardSnapshot. Use the locale and theme controls to
switch tiles; per-frame download is a live-gallery feature.
Native-sized images are convenient for store artwork, but should still be checked against the Android or iOS app before submission when platform rendering details matter.
Requirements: JDK 17+, a WasmGC-capable browser for live gallery work, Android SDK for Android snapshot samples and the café Android app, and Xcode for iOS showcase work.
# Core tests, runtime Wasm, and the prebuilt snapshot viewer jar
./gradlew test :artboard-runtime:jvmTest :artboard-runtime:compileKotlinWasmJs :artboard-viewer-dist:jar
# Live Wasm consumer contract
./gradlew -p samples/minimal artboardDoctor artboardReport compileKotlinWasmJs
# JVM snapshot consumer (no wasmJs)
./gradlew -p samples/light artboardDoctor artboardReport artboardSnapshot artboardExport
# Android-only snapshot consumer (no wasmJs / jvm)
./gradlew -p samples/android-light artboardDoctor artboardReport artboardSnapshot artboardExport
# Café gallery, Android, and iOS verification
./gradlew -p showcase/cafe :shared:artboardExport
./gradlew -p showcase/cafe :androidApp:assembleDebug
./gradlew -p showcase/cafe :shared:iosSimulatorArm64Test :shared:linkDebugFrameworkIosSimulatorArm64For UI or host changes, run the relevant artboardRun, open its printed URL,
check the browser console, and exercise the changed control (including Grid
on a Screen frame in snapshot mode). Keep screenshots and verification artifacts
under /tmp, never in the repository.
The hero animation is recorded locally from the café export (not CI):
./gradlew -p showcase/cafe :shared:artboardExport # once, or when the showcase changes
cd scripts && npm install # once
node record-demo.mjs # writes ../artboard_sample.gif
# or: node record-demo.mjs --build # export + record
# or: node record-demo.mjs --url http://127.0.0.1:8080/Requires Node 20+, ffmpeg on PATH, and Google Chrome (or Playwright Chromium).
The tour drives kind filters, search, layout grid, zoom, theme, pan, frame
selection, and an Arabic locale switch on the Settings screen (via
#frame=…&locale=ar deep links — Popup menus are not scriptable under Playwright).
Artboard is licensed under Apache-2.0. Bundled font notices are in THIRD_PARTY_NOTICES.md.