
Declarative adapter for Microsoft Clarity analytics with UI-friendly APIs to auto-track screens, clicks and session tags; buffers pre-init calls, enforces thread-safety and validates values.
Use Microsoft Clarity in Compose Multiplatform with declarative, simple Kotlin APIs.
Automatically track screens, click events, and session tags across Android and iOS.
Disclaimer: This is an unofficial community library and is not affiliated with, endorsed by, or sponsored by Microsoft. "Clarity" is a trademark of Microsoft Corporation. All platform SDKs are the property of their respective owners.
The raw Microsoft SDK requires you to manage initialization callbacks, thread safety, value length limits, and lifecycle plumbing yourself — on two different platforms. This wrapper removes all of that:
| Task | Raw Microsoft SDK | With Clarity KMP |
|---|---|---|
| Initialize | Handle async init + session callback manually |
createClarityClient(config) — done |
| Tag user before init finishes | Silently dropped ❌ | Buffered & replayed on session start ✅ |
| Track a screen | Manual set/clear on enter & exit | ClarityScreen("Name") { } |
| Track a tap | Custom click handling + event call | Modifier.clarityClickable("name") { } |
| Tag a session | Manual call on every screen | Modifier.clarityTag("plan", "gold") |
| Thread safety | Your responsibility | Enforced (or auto-dispatched to main) |
| Value length limits | Docs only — rejected silently | Validated up front, fail-fast |
| R8/ProGuard keep rules | You find & add them | Shipped with the artifact |
| Compose previews/tests | SDK crashes or no-ops | Safe noOpClarityClient() default |
| Check if recording | N/A | rememberClarityState() |
Add the Compose Multiplatform artifact to your commonMain dependencies in your build.gradle.kts file:
commonMain.dependencies {
// Primary dependency for Compose Multiplatform apps
implementation("io.github.hamdyabdelfatah:clarity-kmp-compose:0.1.0")
}Simply wrap your app content in ClarityProvider and use the built-in tracking components:
// 1. Provide the client to your Composable tree
ClarityProvider(clarityClient) {
// 2. Automatically track screen entry & exit
ClarityScreen("CheckoutPage") {
Button(
modifier = Modifier
// 3. Tag the session when this button is composed
.clarityTag("tier", "gold")
// 4. Track click events automatically
.clarityClickable("buy_now_clicked") {
processPurchase()
},
) {
Text("Buy Now")
}
}
}TrackClarityEvent("promo_banner_shown")val state by rememberClarityState()
if (state == ClarityState.Active) {
Text("Recording session...")
}| Requirement | Version |
|---|---|
| Kotlin | 2.x |
| Compose Multiplatform | 1.x |
| Android minSdk | 24 (API 24) — capture needs API 29+ (Android 10) |
| iOS deployment target | 15.0 |
| Microsoft Clarity iOS SDK | 3.5.3+ (linked via SPM/CocoaPods, see below) |
Below the capture floors (Android 10 / iOS 15) the app runs safely but records nothing —
the client reports ClarityState.Unsupported.
Gradle pulls Microsoft's native Android Compose SDK automatically.
Application.onCreate (must be on the main UI thread):class MyApp : Application() {
lateinit var clarity: ClarityClient
private set
override fun onCreate() {
super.onCreate()
clarity = createClarityClient(
context = this,
config = ClarityConfig(
projectId = "YOUR_PROJECT_ID", // Get this from your Clarity Dashboard
enabled = !BuildConfig.DEBUG, // Turn off in debug/development builds
logLevel = ClarityLogLevel.None,
),
)
}
}clarity instance into your shared KMP App(clarity) entry point. Done! 🎉Because Apple's compiler doesn't let KMP libraries package native compiled binaries inside them, you must link Microsoft's native iOS SDK:
Add Dependency in Xcode:
https://github.com/microsoft/clarity-apps (Version 3.5.3 or newer).Clarity library checkbox.Initialize in your iOS Main Controller:
In your shared module's iosMain source set (e.g., where you define MainViewController):
val clarity = createClarityClient(
config = ClarityConfig(projectId = "YOUR_PROJECT_ID")
)
fun MainViewController() = ComposeUIViewController {
App(clarity)
}You can call these general methods on the clarity client instance inside your shared code:
// Tag your user with their login ID
clarity.setCustomUserId("user_abc_123")clarity.setCustomSessionId("checkout-session-99")clarity.trackEvent("purchase_completed")clarity.tag("plan", "premium")// Apply user consent preferences
clarity.setConsent(ClarityConsent(analyticsStorage = true))The fluent API works anywhere you have the ClarityClient — ViewModels, repositories, use cases:
class CheckoutViewModel(private val clarity: ClarityClient) {
fun checkoutStarted() {
clarity.withScreen("Checkout") { // sets screen for the block, resets after
clarity.tag("plan", "premium") // fluent alias for setCustomTag
}
}
fun purchaseCompleted() {
clarity.trackEvent("purchase_completed")
clarity.userId("user_abc_123") // fluent alias for setCustomUserId
}
fun onAppBackgrounded() {
clarity.ifActive { // guard: only runs while recording
clarity.pause()
}
}
}Every call returns Boolean (true = accepted/applied), so you can log or react to failures
without exceptions. Batch multiple events/tags in one call with sendEvents(...) /
setTags(...).
If you are using an AI Coding Assistant (such as Copilot, Cursor, or ChatGPT) to write tracking code in your app, copy and paste the prompt block below to train the model on how to use this library:
We are using the `Clarity KMP` library to track analytics. Here is how you should write tracking code:
1. Always inject the `ClarityClient` instance to write tracking logic.
2. For screen views, wrap each screen in `ClarityScreen(name) { ... }`.
3. To tag a session from a Composable, use `Modifier.clarityTag(key, value)`.
4. For tracking clicks/taps on layouts, cards, or custom buttons, use the `Modifier.clarityClickable(eventName) { onClick() }` helper instead of a standard `Modifier.clickable`.
5. To log a one-time impression event when a composable shows up, use `TrackClarityEvent(eventName)`.
6. To set custom user IDs, use `clarity.userId(id)`.
7. To track custom events in ViewModels or other shared code, use `clarity.trackEvent(eventName)`.
8. Do not import native com.microsoft.clarity.* classes in commonMain. Use the shared KMP com.hamdy.clarity.* classes.If you are working on a legacy Android View-based app, or a pure UIKit/SwiftUI iOS app with no Compose Multiplatform, use the core lightweight module instead.
commonMain.dependencies {
implementation("io.github.hamdyabdelfatah:clarity-kmp:0.1.0")
}Since you don't have Compose, you must set and clear screen names manually:
// On Screen Enter
clarity.screen("Dashboard")
// On Screen Exit
clarity.screen(null)Or use the block-scoped helper:
clarity.withScreen("Dashboard") {
// Runs block and automatically resets screen name to null when done
}val state by rememberClarityState() — it must be
ClarityState.Active for calls to be recorded. If it stays InitializationAccepted, calls
made before activation are buffered and applied on session start (default behavior).Failed or Unsupported, see the entries below. In debug builds, set
logLevel = ClarityLogLevel.Debug on the config to see native SDK logs.projectId must be your real Clarity dashboard project ID — the sample's
YOUR_PROJECT_ID placeholder records nothing.Unsupported?
Undefined symbols?
https://github.com/microsoft/clarity-apps to your Package Dependencies in Xcode.implementation("io.github.hamdyabdelfatah:clarity-kmp") {
exclude(group = "com.microsoft.clarity", module = "clarity")
}Use Microsoft Clarity in Compose Multiplatform with declarative, simple Kotlin APIs.
Automatically track screens, click events, and session tags across Android and iOS.
Disclaimer: This is an unofficial community library and is not affiliated with, endorsed by, or sponsored by Microsoft. "Clarity" is a trademark of Microsoft Corporation. All platform SDKs are the property of their respective owners.
The raw Microsoft SDK requires you to manage initialization callbacks, thread safety, value length limits, and lifecycle plumbing yourself — on two different platforms. This wrapper removes all of that:
| Task | Raw Microsoft SDK | With Clarity KMP |
|---|---|---|
| Initialize | Handle async init + session callback manually |
createClarityClient(config) — done |
| Tag user before init finishes | Silently dropped ❌ | Buffered & replayed on session start ✅ |
| Track a screen | Manual set/clear on enter & exit | ClarityScreen("Name") { } |
| Track a tap | Custom click handling + event call | Modifier.clarityClickable("name") { } |
| Tag a session | Manual call on every screen | Modifier.clarityTag("plan", "gold") |
| Thread safety | Your responsibility | Enforced (or auto-dispatched to main) |
| Value length limits | Docs only — rejected silently | Validated up front, fail-fast |
| R8/ProGuard keep rules | You find & add them | Shipped with the artifact |
| Compose previews/tests | SDK crashes or no-ops | Safe noOpClarityClient() default |
| Check if recording | N/A | rememberClarityState() |
Add the Compose Multiplatform artifact to your commonMain dependencies in your build.gradle.kts file:
commonMain.dependencies {
// Primary dependency for Compose Multiplatform apps
implementation("io.github.hamdyabdelfatah:clarity-kmp-compose:0.1.0")
}Simply wrap your app content in ClarityProvider and use the built-in tracking components:
// 1. Provide the client to your Composable tree
ClarityProvider(clarityClient) {
// 2. Automatically track screen entry & exit
ClarityScreen("CheckoutPage") {
Button(
modifier = Modifier
// 3. Tag the session when this button is composed
.clarityTag("tier", "gold")
// 4. Track click events automatically
.clarityClickable("buy_now_clicked") {
processPurchase()
},
) {
Text("Buy Now")
}
}
}TrackClarityEvent("promo_banner_shown")val state by rememberClarityState()
if (state == ClarityState.Active) {
Text("Recording session...")
}| Requirement | Version |
|---|---|
| Kotlin | 2.x |
| Compose Multiplatform | 1.x |
| Android minSdk | 24 (API 24) — capture needs API 29+ (Android 10) |
| iOS deployment target | 15.0 |
| Microsoft Clarity iOS SDK | 3.5.3+ (linked via SPM/CocoaPods, see below) |
Below the capture floors (Android 10 / iOS 15) the app runs safely but records nothing —
the client reports ClarityState.Unsupported.
Gradle pulls Microsoft's native Android Compose SDK automatically.
Application.onCreate (must be on the main UI thread):class MyApp : Application() {
lateinit var clarity: ClarityClient
private set
override fun onCreate() {
super.onCreate()
clarity = createClarityClient(
context = this,
config = ClarityConfig(
projectId = "YOUR_PROJECT_ID", // Get this from your Clarity Dashboard
enabled = !BuildConfig.DEBUG, // Turn off in debug/development builds
logLevel = ClarityLogLevel.None,
),
)
}
}clarity instance into your shared KMP App(clarity) entry point. Done! 🎉Because Apple's compiler doesn't let KMP libraries package native compiled binaries inside them, you must link Microsoft's native iOS SDK:
Add Dependency in Xcode:
https://github.com/microsoft/clarity-apps (Version 3.5.3 or newer).Clarity library checkbox.Initialize in your iOS Main Controller:
In your shared module's iosMain source set (e.g., where you define MainViewController):
val clarity = createClarityClient(
config = ClarityConfig(projectId = "YOUR_PROJECT_ID")
)
fun MainViewController() = ComposeUIViewController {
App(clarity)
}You can call these general methods on the clarity client instance inside your shared code:
// Tag your user with their login ID
clarity.setCustomUserId("user_abc_123")clarity.setCustomSessionId("checkout-session-99")clarity.trackEvent("purchase_completed")clarity.tag("plan", "premium")// Apply user consent preferences
clarity.setConsent(ClarityConsent(analyticsStorage = true))The fluent API works anywhere you have the ClarityClient — ViewModels, repositories, use cases:
class CheckoutViewModel(private val clarity: ClarityClient) {
fun checkoutStarted() {
clarity.withScreen("Checkout") { // sets screen for the block, resets after
clarity.tag("plan", "premium") // fluent alias for setCustomTag
}
}
fun purchaseCompleted() {
clarity.trackEvent("purchase_completed")
clarity.userId("user_abc_123") // fluent alias for setCustomUserId
}
fun onAppBackgrounded() {
clarity.ifActive { // guard: only runs while recording
clarity.pause()
}
}
}Every call returns Boolean (true = accepted/applied), so you can log or react to failures
without exceptions. Batch multiple events/tags in one call with sendEvents(...) /
setTags(...).
If you are using an AI Coding Assistant (such as Copilot, Cursor, or ChatGPT) to write tracking code in your app, copy and paste the prompt block below to train the model on how to use this library:
We are using the `Clarity KMP` library to track analytics. Here is how you should write tracking code:
1. Always inject the `ClarityClient` instance to write tracking logic.
2. For screen views, wrap each screen in `ClarityScreen(name) { ... }`.
3. To tag a session from a Composable, use `Modifier.clarityTag(key, value)`.
4. For tracking clicks/taps on layouts, cards, or custom buttons, use the `Modifier.clarityClickable(eventName) { onClick() }` helper instead of a standard `Modifier.clickable`.
5. To log a one-time impression event when a composable shows up, use `TrackClarityEvent(eventName)`.
6. To set custom user IDs, use `clarity.userId(id)`.
7. To track custom events in ViewModels or other shared code, use `clarity.trackEvent(eventName)`.
8. Do not import native com.microsoft.clarity.* classes in commonMain. Use the shared KMP com.hamdy.clarity.* classes.If you are working on a legacy Android View-based app, or a pure UIKit/SwiftUI iOS app with no Compose Multiplatform, use the core lightweight module instead.
commonMain.dependencies {
implementation("io.github.hamdyabdelfatah:clarity-kmp:0.1.0")
}Since you don't have Compose, you must set and clear screen names manually:
// On Screen Enter
clarity.screen("Dashboard")
// On Screen Exit
clarity.screen(null)Or use the block-scoped helper:
clarity.withScreen("Dashboard") {
// Runs block and automatically resets screen name to null when done
}val state by rememberClarityState() — it must be
ClarityState.Active for calls to be recorded. If it stays InitializationAccepted, calls
made before activation are buffered and applied on session start (default behavior).Failed or Unsupported, see the entries below. In debug builds, set
logLevel = ClarityLogLevel.Debug on the config to see native SDK logs.projectId must be your real Clarity dashboard project ID — the sample's
YOUR_PROJECT_ID placeholder records nothing.Unsupported?
Undefined symbols?
https://github.com/microsoft/clarity-apps to your Package Dependencies in Xcode.implementation("io.github.hamdyabdelfatah:clarity-kmp") {
exclude(group = "com.microsoft.clarity", module = "clarity")
}