name: compose-preview description: Render Compose @Preview functions to PNG outside Android Studio. Use this to verify UI changes, iterate on designs, and compare before/after states across Android (Jetpack Compose) and Compose Multiplatform Desktop projects.
Compose Preview
Render @Preview composables to PNG images without launching Android Studio.
Works on both Android (Jetpack Compose via Robolectric) and Compose Multiplatform
Desktop (via ImageComposeScene + Skia).
Maintained at github.com/yschimke/compose-ai-tools
under skills/compose-preview/. The bundle this documentation ships with is
v0.9.3.
<!-- x-release-please-end -->Run compose-preview --version to check the installed bundle, compose-preview doctor
to compare against the latest release (warns when the local copy trails), and
compose-preview update to re-run the bootstrap installer.
What this skill provides
- A Gradle plugin (
ee.schimke.composeai.preview) that discovers@Previewannotations from compiled classes and registers rendering tasks. - A
compose-previewCLI that drives the Gradle build via the Tooling API and surfaces rendered PNG paths. - A VS Code extension with a preview panel, CodeLens and hover actions on
@Previewfunctions, and commands for rendering all or a single file.
Gradle tasks
Applied to each module that declares the plugin:
| Task | Purpose |
|---|---|
:<module>:discoverPreviews | Scan compiled classes, emit build/compose-previews/previews.json. |
:<module>:renderAllPreviews | Discover + render every @Preview to PNG under build/compose-previews/. |
:<module>:discoverAndroidResources | Walk res/drawable* + res/mipmap*, parse AndroidManifest.xml, emit build/compose-previews/resources.json. See design/RESOURCE_PREVIEWS.md. |
:<module>:renderAndroidResources | Render every discovered XML drawable / mipmap to PNG / GIF under build/compose-previews/renders/resources/. |
All Gradle-cacheable with strict configuration caching — unchanged inputs produce no re-work.
CLI
The CLI auto-detects the Gradle project root (walks up for gradlew) and, by
default, every module that has the plugin applied.
compose-preview <command> [options]
Commands:
show Discover + render previews; print id, path, sha256, changed flag
list List discovered previews
render Render previews; with --output copies a single match to disk
a11y Render previews and print ATF accessibility findings
extensions run a11y-annotated-preview.render
One-shot a11y hierarchy + ATF + annotated overlay render
doctor Verify Java 17+ + project compatibility (run before Setup)
Options:
--module <name> Target a single module (default: auto-detect)
--variant <variant> Android build variant (default: debug)
--filter <pattern> Case-insensitive substring match on preview id
--id <exact> Exact match on preview id
--json Emit JSON (show, list)
--output <path> Copy matched preview PNG to this path (render)
--progress Print per-task milestone/heartbeat lines to stderr
--verbose, -v Full Gradle build output (implies --progress)
--timeout <seconds> Gradle build timeout (default: 300)
OSC 9;4 terminal progress (native taskbar/tab progress bar) is on by default
in a TTY and auto-disables when stdout is piped. Textual progress lines are
opt-in via --progress.
Exit codes: 0 success, 1 build failure, 2 render failure, 3 no previews.
--json output per entry includes the full PreviewParams (device, widthDp,
heightDp, fontScale, uiMode, …), the absolute pngPath, the sha256 of
the PNG bytes, and a changed boolean computed against the previous
invocation. State is persisted per-module under
<module>/build/compose-previews/.cli-state.json and gets wiped by
./gradlew clean.
Iterating on a design
list → edit → show --json → read the PNGs whose changed: true. Gradle
caching means re-renders only redo what changed; the changed flag lets
agents skip reading PNGs that didn't move. Always read the PNG after a UI
change — don't assume the change looks correct.
Designing composables for previewability
@Preview only calls composables with zero arguments (or all-default), so
anything taking a ViewModel, repository, or DI-injected service can't be
previewed directly. Apply state hoisting: split each screen into a
stateful wrapper (wires runtime deps) and a stateless inner composable that
takes state + callbacks. Preview the stateless layer with hand-rolled
fixtures.
Agent guidance: if asked to iterate on a composable that accepts a
ViewModel or injected dependency, first propose extracting a stateless
inner composable and preview that. The one-time extraction unlocks the
fast compose-preview iteration loop for every future change on that
screen. See design/STATE_HOISTING.md for
the pattern with code.
Setup
The plugin is on Maven Central — most projects already have mavenCentral()
in their plugin repositories, so no credentials or extra registry config.
Agents: check first, install only with user consent. Run
compose-preview --version && compose-preview doctor to see whether the CLI
is already available — if it is, you're done. Don't blindly re-run the
installer between previews; that's how runaway download loops start.
If the CLI is missing, surface this command to the user and let them run
it (or copy it back to you). The installer refuses to download without
--yes, which exists exactly so agents can't pull binaries by accident:
curl -fsSL https://raw.githubusercontent.com/yschimke/compose-ai-tools/main/scripts/install.sh \
| bash -s -- --yes
compose-preview doctor
To upgrade an existing install, swap --yes for --upgrade (or set
COMPOSE_PREVIEW_ACCEPT_UPGRADE=1). Without either flag the script prints
instructions and exits — no tarball is fetched.
doctor verifies Java 17+ on PATH (JDK 21/25 are fine — the renderer is
compiled to JDK 17 bytecode). If the install path isn't on PATH, the
script prints the exact command to add it.
From a Compose project root, install the MCP server descriptors:
compose-preview mcp install # auto-detects Antigravity
compose-preview mcp install --antigravity # force the Antigravity config write
Apply the plugin in <module>/build.gradle.kts:
plugins {
id("ee.schimke.composeai.preview") version "0.9.3"
}
composePreview {
variant.set("debug") // Android build variant (default: "debug")
sdkVersion.set(35) // Robolectric SDK version (default: 35)
enabled.set(true) // set false to skip task registration
}
<!-- x-release-please-end -->
CMP Desktop projects additionally need
implementation(compose.components.uiToolingPreview) — the bundled @Preview
annotation has SOURCE retention and is invisible to classpath scanning
otherwise.
The Android variant relies on Robolectric with native graphics; the plugin
takes care of the relevant test/tooling dependencies. Agents MUST NOT run
internal tasks like collectPreviewInfo — they're wired by the plugin itself.
Reference docs
Loaded on demand. Read only what the current task needs.
| Path | When to read |
|---|---|
| design/PERMISSIONS.md | Setting up agent allowlists; staging PNGs outside build/. |
| design/STATE_HOISTING.md | Full state-hoisting pattern with code examples. |
| design/CAPTURE_MODES.md | Multi-preview annotations, @AnimatedPreview GIFs, MCP scripted recordings, paused-clock snapshots, scrolling captures. |
| design/A11Y.md | ATF accessibility checks (compose-preview a11y). |
| design/DATA_PRODUCTS.md | Structured per-render data (a11y findings + hierarchy, layout tree, recomposition heat-map, …) via MCP tools and on-disk Gradle output. |
| design/MCP.md | Driving compose-preview from an MCP-aware agent host (push notifications, multi-workspace, in-process server bundled in the CLI). |
| design/CMP_SHARED.md | Compose Multiplatform :shared modules (commonMain previews via Desktop pipeline). |
| design/RESOURCE_PREVIEWS.md | Android XML resources (<vector>, <animated-vector>, <adaptive-icon>). |
| design/WEAR_UI.md | Wear OS Material 3 Expressive design. |
| design/WEAR_TILES.md | Wear Tiles (protolayout, not Compose). |
| design/REMOTE_COMPOSE.md | Remote Compose dialect + RemoteDocument. |
| design/CLAUDE_CLOUD.md | Running compose-preview in Claude Code cloud sandboxes (allowlist, JDK, install paths). |
| design/VSCODE.md | VS Code extension (humans, not agents). |
Related skill
PR-review and CI workflows live in the sibling
compose-preview-review skill:
authoring agent-opened PRs, reviewing UI PRs locally (base + head render,
diff, text comment), and wiring compose-preview/main baselines +
PR-comment GitHub Actions. The bootstrap installer
(scripts/install.sh)
sets up both skills together.