Warp Widget
CommunityDevAtrii
Create cross-platform home screen widgets for Kotlin Multiplatform (Android Glance & iOS WidgetKit/SwiftUI) using the WARP framework. Use when building or updating WARP widgets, defining widget states, action handlers, layout composables, themes, Android Glance receivers, or iOS WidgetKit bridges.
Overview
- Repository
- DevAtrii/Warp
- Publisher
- DevAtrii
- Stars
- 38
- Forks
- 1
- Bundled files
- 0
- License
- MIT
- Updated
- Aug 11, 2026
Installation
tmp=$(mktemp -d)
git clone --depth 1 https://github.com/DevAtrii/Warp.git "$tmp"
mkdir -p ~/.claude/skills
cp -R "$tmp/.agents/skills/warp-widget" ~/.claude/skills/warp-widget
rm -rf "$tmp"tmp=$(mktemp -d)
git clone --depth 1 https://github.com/DevAtrii/Warp.git "$tmp"
mkdir -p ~/.codex/skills
cp -R "$tmp/.agents/skills/warp-widget" ~/.codex/skills/warp-widget
rm -rf "$tmp"Install the Warp Widget agent skill from https://github.com/DevAtrii/Warp/tree/main/.agents/skills/warp-widget into this project's skills folder.Instructions
Creating Home Screen Widgets with WARP in KMP
This skill provides step-by-step instructions, design patterns, composables reference, and platform integrations for creating cross-platform home screen widgets using WARP (Widget Abstraction Rendering Pipeline) in Kotlin Multiplatform (KMP).
📚 Official Documentation: Complete live documentation and page links are available at
https://warp.atherio.dev(Sitemap index:https://warp.atherio.dev/sitemap.xml).
1. Core Architecture & Mental Model
WARP compiles a Compose-style @Composable DSL in commonMain into a serializable AST (WarpNode), which is rendered natively on Android (Jetpack Glance) and iOS (WidgetKit & SwiftUI).
Architectural Rules for AI Generators:
- Never use standard Compose UI or Foundation composables (
Column,Row,Text,Button,Modifier,dp,spfromandroidx.compose.ui). Always use WARP primitives (WarpColumn,WarpRow,WarpText,WarpButton,WarpModifier,dp,spfromcom.atriidev.warp_runtime.*). - Keep Widgets Lightweight: Widgets display pre-fetched state. Do not execute network calls or heavy DI inside widget render loops.
- Use Serialized Actions: Taps must map to serializable action classes (
CounterActions.Increment.asClickAction()) handled by aWarpActionHandler.
2. Shared Widget Template (commonMain)
Every WARP widget requires 4 components defined in commonMain:
2.1 State & Action Definitions
package com.example.app.widget
import kotlinx.serialization.Serializable
/** 1. Widget State Payload */
@Serializable
data class MyWidgetState(
val title: String = "Hello WARP",
val count: Int = 0,
)
/** 2. Type-Safe User Action Hierarchy */
@Serializable
sealed class MyWidgetActions {
@Serializable data object Increment : MyWidgetActions()
@Serializable data object Decrement : MyWidgetActions()
@Serializable data object Reset : MyWidgetActions()
}2.2 Action Handler
import com.atriidev.warp_ui.WarpActionHandler
import com.atriidev.warp_widget.WarpWidgetSession
import com.atriidev.warp_widget.updateWarpWidgetState
class MyWidgetActionHandler(
private val session: WarpWidgetSession,
) : WarpActionHandler<MyWidgetActions>(MyWidgetActions.serializer()) {
override suspend fun onAction(action: MyWidgetActions) {
updateWarpWidgetState(session, MyWarpWidget) { state ->
when (action) {
MyWidgetActions.Increment -> state.copy(count = state.count + 1)
MyWidgetActions.Decrement -> state.copy(count = state.count - 1)
MyWidgetActions.Reset -> state.copy(count = 0)
}
}
}
}2.3 Asset Identifiers
import com.atriidev.warp_runtime.nodes.assets.WarpAssetId
object MyWidgetAssets {
val Plus = WarpAssetId("plus") // Maps to SF Symbol "plus" on iOS & R.drawable on Android
val Minus = WarpAssetId("minus")
}2.4 Complete WarpWidget Object
import androidx.compose.runtime.Composable
import com.atriidev.warp_runtime.compose.*
import com.atriidev.warp_runtime.nodes.actions.asClickAction
import com.atriidev.warp_runtime.nodes.modifiers.WarpColor
import com.atriidev.warp_runtime.nodes.modifiers.WarpModifier
import com.atriidev.warp_runtime.nodes.style.*
import com.atriidev.warp_runtime.unit.dp
import com.atriidev.warp_runtime.unit.sp
import com.atriidev.warp_ui.WarpActionHandler
import com.atriidev.warp_widget.WarpWidget
import com.atriidev.warp_widget.WarpWidgetSession
import com.atriidev.warp_widget.WarpWidgetStateScope
import com.atriidev.warp_widget.api.WidgetEnvironment
import com.atriidev.warp_widget.ui.WarpTheme
object MyWarpWidget : WarpWidget<MyWidgetState>(MyWidgetState.serializer()) {
override val id: String = "MyWidget" // Must match iOS Widget target kind
override val iosGroupId: String = "group.com.example.app" // Xcode App Group ID
override val stateScope: WarpWidgetStateScope = WarpWidgetStateScope.Shared // Shared or Instance
override suspend fun defaultState(): MyWidgetState = MyWidgetState()
@Composable
override fun Content(session: WarpWidgetSession, state: MyWidgetState) {
val env = session.environment
WarpTheme(environment = env) {
WarpBox(
modifier = WarpModifier
.fillMaxSize()
.background(WarpTheme.colors.widgetBackground)
.padding(16.dp),
contentAlignment = WarpContentAlignment.Center,
) {
WarpColumn(
horizontalAlignment = WarpHorizontalAlignment.CenterHorizontally,
) {
WarpText(
text = "${state.title}: ${state.count}",
style = WarpTextStyle(
fontSize = 20.sp,
fontWeight = WarpFontWeight.Bold,
color = WarpTheme.colors.onSurface,
),
)
WarpSpacer(modifier = WarpModifier.height(12.dp))
WarpRow {
WarpButton(
text = "−",
onClick = MyWidgetActions.Decrement.asClickAction(),
modifier = WarpModifier.size(40.dp),
)
WarpSpacer(modifier = WarpModifier.width(12.dp))
WarpButton(
text = "+",
onClick = MyWidgetActions.Increment.asClickAction(),
modifier = WarpModifier.size(40.dp),
)
}
}
}
}
}
override fun clickHandlers(session: WarpWidgetSession): List<WarpActionHandler<*>> =
listOf(MyWidgetActionHandler(session))
}3. WARP Composables & Modifiers Quick Reference
Layout Composables
WarpColumn: Vertical column layout (verticalAlignment,horizontalAlignment).WarpRow: Horizontal row layout (horizontalAlignment,verticalAlignment).WarpBox: Stacked layer layout (contentAlignment).WarpSpacer: Empty spacing box (width,height,weight).
Content Composables
WarpText(text, modifier, style, maxLines)WarpButton(text/content, onClick, modifier, style, colors, enabled)WarpImage(asset, contentDescription, modifier, contentScale, tint)WarpLink(deeplink, modifier, androidIntentFlags, content)WarpProgressIndicator(modifier, style, progress, color, backgroundColor)WarpDivider(modifier, thickness, color)
Modifiers (WarpModifier)
- Dimensions:
.fillMaxSize(),.fillMaxWidth(),.fillMaxHeight(),.size(dp),.width(dp),.height(dp),.weight(flex) - Spacing:
.padding(all),.padding(horizontal, vertical),.padding(start, top, end, bottom) - Appearance:
.background(WarpColor),.cornerRadius(dp),.border(width, WarpColor),.alpha(float),.visibility(...) - Interactions:
.clickable(action.asClickAction())
4. Theming & Adaptive Size Scaling
Light / Dark Theme (WarpTheme)
Access dynamic light/dark colors mapped to platform defaults (Material 3 on Android, iOS System Blue on iOS):
val colors = WarpTheme.colors
WarpText("Title", style = WarpTextStyle(color = colors.onSurface))Adaptive Layout Buckets (WarpAdaptiveSize)
Scale layouts dynamically across Small (2x2), Medium (4x2), and Large (4x4) sizes:
// 1. Swap full layouts:
WarpAdaptiveContent(
environment = env,
small = { SmallLayout(state) },
medium = { MediumLayout(state) },
large = { LargeLayout(state) },
)
// 2. Pick adaptive values (padding, font sizes, icon sizes):
val iconSize = env.adaptiveValue(small = 16.dp, medium = 20.dp, large = 24.dp)
val fontSize = env.adaptiveValue(small = 14.sp, medium = 18.sp, large = 24.sp)5. Android Host Integration (androidMain)
Create MyWidgetGlance.kt in shared/src/androidMain:
package com.example.app.widget
import com.atriidev.warp_ui.glance.WarpDrawableAsset
import com.atriidev.warp_widget.WarpGlanceWidget
import com.atriidev.warp_widget.WarpGlanceWidgetReceiver
import com.atriidev.warp_widget.WarpWidgetHostApi
import com.example.app.R
class MyWidgetReceiver : WarpGlanceWidgetReceiver() {
override fun createGlanceWidget() = MyGlanceAppWidget(createWarpWidget())
override fun createWarpWidget(): WarpWidgetHostApi = MyWarpWidget
}
class MyGlanceAppWidget(
private val widget: WarpWidgetHostApi,
) : WarpGlanceWidget() {
override fun createWarpWidget(): WarpWidgetHostApi = widget
override fun assets(): List<WarpDrawableAsset> = listOf(
WarpDrawableAsset(MyWidgetAssets.Plus, R.drawable.ic_plus),
WarpDrawableAsset(MyWidgetAssets.Minus, R.drawable.ic_minus),
)
}Register in shared/src/androidMain/AndroidManifest.xml:
<receiver android:name="com.example.app.widget.MyWidgetReceiver" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.intent.action.CONFIGURATION_CHANGED" />
<action android:name="android.intent.action.UI_MODE_CHANGED" />
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/my_app_widget_info" />
</receiver>6. iOS Host Integration & PlatformContext
6.1 iOS Swift WidgetKit Boilerplate Generation
Setting up Swift WidgetKit structures (WidgetBundle, AppIntent, TimelineProvider, WidgetView, AppEntity) can be completed using one of two options:
- Option A (Web Generator - Recommended): If browser access is available, use the interactive Warp Widget Wizard:
https://warp.atherio.dev/wizard.html(orwizard.htmllocally). Enter your iOS Widget Name (e.g.Counter), Kotlin WARP Object Name (e.g.CounterWarpWidget), and Shared Module Name (e.g.Shared) to download the 6 generated Swift boilerplate files. - Option B (Repository Reference Files): If browser access is unavailable, download or copy the template Swift files directly from
https://github.com/DevAtrii/Warp/tree/main/iosApp/CounterWidget(oriosApp/CounterWidget/in the local repository) and manually update the widget names andSharedframework module imports.
6.2 Obtaining PlatformContext across Platforms
- Compose UI (
commonMain):val context = rememberPlatformContext(widget = MyWarpWidget) - Android (
androidMain): Pass AndroidContext. - iOS (
iosMainor Swift):// Kotlin iosMain: val context = getPlatformContext(widget = MyWarpWidget)// Swift: let context = getPlatformContext(widget: MyWarpWidget.shared)
7. Programmatic State Updates from Main App
Trigger state updates or read saved state from ViewModels, background workers, or services:
import com.atriidev.warp_widget.updateWarpWidgetState
import com.atriidev.warp_widget.readWarpWidgetState
import com.atriidev.warp_widget.listWarpWidgetIds
// Update state:
suspend fun updateWidgetCount(context: PlatformContext, newCount: Int) {
updateWarpWidgetState(context, MyWarpWidget) { state ->
state.copy(count = newCount)
}
}8. Generation Checklist for AI Assistants
When asked to generate a WARP widget:
- Define
@SerializableState data class. - Define
@SerializableAction sealed hierarchy. - Implement
WarpActionHandlermutating state viaupdateWarpWidgetState. - Define
object WidgetName : WarpWidget<State>. - Specify matching
idandiosGroupId. - Implement
@Composable Contentusing WARP DSL (WarpColumn,WarpRow,WarpTheme,WarpAdaptiveContent). - Wire click handlers in
override fun clickHandlers(...). - Provide Android Glance
ReceiverandAppWidgetboilerplate. - Provide iOS setup guidance via Widget Wizard (
https://warp.atherio.dev/wizard.html) or reference Swift templates (https://github.com/DevAtrii/Warp/tree/main/iosApp/CounterWidget).
FAQ
What does Warp Widget do?
Create cross-platform home screen widgets for Kotlin Multiplatform (Android Glance & iOS WidgetKit/SwiftUI) using the WARP framework. Use when building or updating WARP widgets, defining widget states, action handlers, layout composables, themes, Android Glance receivers, or iOS WidgetKit bridges.
Who made Warp Widget?
DevAtrii (a community member) publishes it in the DevAtrii/Warp repository on GitHub.
Is Warp Widget free?
Yes. Warp Widget is open source, published by DevAtrii under the MIT license.
What comes with Warp Widget?
Just its instructions. Everything the agent needs is in one SKILL.md file.
Which agents can use Warp Widget?
Any coding agent that reads SKILL.md files, including Claude Code, Codex, Cursor, GitHub Copilot and Gemini CLI. The agent loads the skill when a task matches its description.
What are agent skills?
An agent skill is a folder with a SKILL.md file: instructions that teach a coding agent how to do one kind of task, and optionally files it can read while it works. The agent keeps only the skill's name and description in mind and reads the rest when a task calls for it.
How are agent skills different from MCP servers?
An MCP server gives an agent new tools to call, code that runs somewhere and returns results. A skill gives it knowledge: how to approach a task with the tools it already has. Many agents use both.
How can my agent test my Android app?
Robomori lets coding agents create, start and control Android emulators and devices from one command, so they can install your app, take screenshots and check dark mode, font sizes and languages.