Repository files navigation

ComposeGuard

JetBrains PluginDownloadsLicenseProfileAndroid Weekly

Catch Jetpack Compose mistakes as you type — 39 best-practice rules from the Compose Rules guidelines, surfaced live in Android Studio & IntelliJ IDEA with inline highlights, gutter icons, and one-click fixes.


Preview

ComposeGuard Preview

Why ComposeGuard?

The Compose Rules catch the subtle mistakes that hurt Compose code — missing Modifier parameters, un-remembered state, unstable collections, reused modifiers, and dozens more. ComposeGuard brings those checks into the editor, so you fix them while the code is still fresh instead of discovering them in a build log or a code review.

  • Instant — analysis runs as you type; no build, no Gradle task, no CI round-trip.
  • 🎯 Accurate — rules are PSI-based and tuned to avoid false positives on valid patterns (overrides, scoped slots, mutually-exclusive branches, run-once effects, …).
  • 🛠 Actionable — most violations come with a quick fix (Alt+Enter) and a detailed explanation of why it matters.
  • 🎚 Configurable — enable/disable any rule or whole category, or suppress per declaration.

Table of Contents

Features

  • Real-time highlighting — violations appear as colored underlines while you edit.
  • Gutter icons — a color-coded dot per @Composable summarizes its status at a glance:
    • 🔴 Error 🟠 Warning ⚪ Weak warning 🔵 Info
  • Inline hints — compact badges next to function names show rule violations.
  • Hover tooltips — every violation explains the problem, the reasoning, and the fix.
  • Quick fixes — rename, add a modifier parameter, wrap in remember, switch to a type-specific state, make a preview private, swap to an immutable collection, and more.
  • 39 rules across 6 categories — see the full Rule Reference.

Installation

Install from Marketplace

  1. Open Android Studio or IntelliJ IDEA.
  2. Go to SettingsPluginsMarketplace.
  3. Search for ComposeGuard.
  4. Click Install and restart when prompted.

Or install directly from the JetBrains Marketplace.

Quick Start

Once installed, ComposeGuard automatically analyzes any Kotlin file containing @Composable functions — no configuration required. Here are a few things it catches:

// 🟠 Naming: Unit-returning composables should be PascalCase
@Composable
funuserCard(user:User) { } // → rename to "UserCard"// 🟠 Modifier: public UI composables should expose a Modifier
@Composable
funProductCard(product:Product) { // → add `modifier: Modifier = Modifier`Column { Text(product.name) }
}
// 🔴 State: state must be remembered
@Composable
funCounter() {
val count = mutableStateOf(0) // → wrap in remember { }
}
// 🟠 Stricter: prefer stable collections
@Composable
funItemList(items:List<Item>) { } // → use ImmutableList<Item>

Hover any highlight for the full explanation, or press Alt+Enter to apply a fix.

Suppressing Rules

To intentionally allow a violation, annotate the declaration with @Suppress using the rule id (the same id shown in the warning, e.g. ModifierRequired). The quick fix can insert this for you:

@Suppress("ModifierRequired")
@Composable
funSplashLogo() {
Image(painterResource(R.drawable.logo), contentDescription =null)
}
// Multiple rules at once:
@Suppress("ModifierRequired", "ComposableNaming")
@Composable
funsplash() { /* ... */ }

Suppression works at the function, property, or class level. To turn rules off project-wide instead, use Configuration.

Rule Reference

ComposeGuard ships 39 rules based on the Compose Rules guidelines. Severity legend: 🔴 Error · 🟠 Warning · ⚪ Weak warning · 🔵 Info.

Naming

Rule idChecksSeverity
ComposableNamingUnit-returning composables use PascalCase; value-returning use camelCase🟠
CompositionLocalNamingCompositionLocal properties are prefixed with Local🟠
PreviewNaming@Preview functions reference Preview in their name
MultipreviewNamingMultipreview annotation classes reference Preview
ComposableAnnotationNaming@ComposableTargetMarker annotations end with Composable
EventParameterNamingEvent lambdas use present tense (onClick, not onClicked)

Modifiers

Rule idChecksSeverity
ModifierRequiredPublic, UI-emitting composables expose a Modifier parameter🟠
ModifierDefaultValuemodifier parameters default to Modifier🟠
ModifierNamingThe main modifier is named modifier; others follow xModifier
ModifierTopMostThe modifier is applied to the root-most layout🟠
ModifierReuseThe same modifier isn't applied to multiple live nodes🟠
ModifierOrderModifier chain order is intentional (e.g. padding before clickable)🟠
AvoidComposedPrefer Modifier.Node over the deprecated composed { } factory🟠

State

Rule idChecksSeverity
RememberStatemutableStateOf and friends are wrapped in remember { }🔴
TypeSpecificStatePrimitives use mutableIntStateOf / mutableFloatStateOf / …🟠
DerivedStateOfCandidateValues computed from state use derivedStateOf🟠
FrequentRecompositionHot observable sources use lifecycle-aware collection🟠
DeferStateReadsFast-changing state reads are deferred to lambda modifiers🟠
HoistStateState is hoisted to the appropriate level🔵
MutableStateParameterPass value + callback instead of a MutableState parameter🟠

Parameters

Rule idChecksSeverity
ParameterOrderingOrder is required → modifier → optional → trailing content
TrailingLambdaThe content slot is the trailing lambda; event handlers are not
MutableParameterAvoid inherently mutable types (MutableList, ArrayList, …) as parameters🟠
ExplicitDependenciesMake injected ViewModels explicit parameters
ViewModelForwardingDon't forward a ViewModel into another composable🟠

Composables & Effects

Rule idChecksSeverity
ContentEmissionA composable emits content or returns a value, not both🟠
MultipleContentEmittersA composable emits a single piece of content🟠
ContentSlotReusedA content slot isn't invoked more than once on the same pass🟠
EffectKeysChanging captured values are passed as effect keys🟠
LambdaParameterInEffectLambda parameters used in effects are wrapped in rememberUpdatedState🟠
MovableContentmovableContentOf is remembered🔴
PreviewVisibility@Preview composables are private🟠
ComponentDefaultsVisibilityA <Component>Defaults object matches its composable's visibility🟠
LazyListMissingKeyLazy list items provide a stable key🔵
ComposableNestingDepthComposables are not nested deeper than the configured limit (opt-in)
LazyListContentTypeHeterogeneous lazy lists set a contentType🔵

Stricter

Rule idChecksSeverity
UnstableCollectionsPrefer ImmutableList / PersistentList over List, Set, Map🟠
CompositionLocalAllowlistCustom CompositionLocals are declared only when allowlisted (opt-in)🟠
Material2UsageMigrate androidx.compose.material (M2) imports to Material 3🔵

Suppress any rule with @Suppress("<RuleId>"), or toggle it in Settings → Tools → ComposeGuard.

Statistics Dashboard

ComposeGuard includes a tool window that tracks rule violations across your project.

ComposeGuard Statistics Dashboard

  • On-demand project scan — press Scan Project to count violations across every Kotlin file.
  • Category breakdown — see violations grouped by rule category.
  • Rule-level details — drill into specific rules.
  • Project overview — track overall code-quality trends.
  • Export — save the last scan as JSON or SARIF for CI dashboards and code-scanning tools.

Open it from ViewTool WindowsComposeGuard, or click a ComposeGuard gutter icon.

Configuration

Configure ComposeGuard at SettingsToolsComposeGuard.

ComposeGuard Settings - Disable Rules

  • Enable All Rules — master switch that selects or clears every rule at once.
  • Display options — toggle gutter icons and inlay hints.
  • Rule configuration — enable/disable individual rules or entire categories.
  • Analyze test sources — uncheck to leave test source roots alone.

Project configuration (.editorconfig)

ComposeGuard reads the same compose_* keys that the upstream Compose Rules ktlint ruleset uses, so a team can commit one .editorconfig and share it between the IDE plugin and CI. Keys are read from the nearest .editorconfig (walking up to the one marked root = true) in any section that applies to Kotlin files, for example [*.{kt,kts}]. Lists are comma-separated; entries containing regex metacharacters are treated as regular expressions.

KeyAffectsMeaning
compose_allowed_composable_function_namesComposableNamingNames (or regexes) exempt from the PascalCase/camelCase check
compose_content_emittersContentEmission, MultipleContentEmitters, ModifierRequired, TrailingLambdaExtra composables that count as emitting UI
compose_content_emitters_denylistsame as aboveComposables that must never count as emitting UI
compose_check_modifiers_for_visibilityModifierRequiredonly_public (default), public_and_internal, or all
compose_modifier_missing_ignore_annotatedModifierRequiredAnnotation names whose composables are skipped
compose_custom_modifiersModifierRequired, ModifierNaming, ModifierDefaultValue, ParameterOrdering, TrailingLambdaExtra types treated as Modifier (e.g. GlanceModifier)
compose_treat_as_lambdaParameterOrdering, TrailingLambdaType aliases treated as plain lambdas
compose_treat_as_composable_lambdaParameterOrdering, TrailingLambdaType aliases treated as @Composable content slots
compose_view_model_factoriesExplicitDependenciesExtra ViewModel factory functions
compose_allowed_composition_localsExplicitDependencies, CompositionLocalAllowlistCompositionLocals that may be read or declared
compose_allowed_state_holder_namesViewModelForwardingType name regexes that are not treated as forwarded ViewModels
compose_allowed_forwardingViewModelForwardingComposables a ViewModel may be forwarded to
compose_allowed_forwarding_of_typesViewModelForwardingViewModel types that may be forwarded
compose_allowed_from_m2Material2UsageMaterial 2 imports (or package prefixes) that are allowed
compose_allowed_lambda_parameter_namesEventParameterNamingEvent parameter names exempt from the present-tense check
compose_preview_naming_strategyPreviewNaminganywhere (default), suffix, or prefix
compose_composable_nesting_depth_thresholdComposableNestingDepthMaximum nesting depth (default 3)
compose_disallow_material2, compose_disallow_unstable_collections, compose_preview_naming_enabled, compose_composable_nesting_depth_enabledrule enablementtrue turns the rule on for this project regardless of IDE settings
root = true
[*.{kt,kts}]compose_allowed_from_m2 = androidx.compose.material.icons
compose_treat_as_composable_lambda = Slot
compose_composable_nesting_depth_enabled = true
compose_composable_nesting_depth_threshold = 4

Adopting ComposeGuard in an existing codebase

Adding the plugin to a large legacy project? Roll it out gradually instead of facing every warning at once:

  1. Start with the Stricter category off (Material2Usage, UnstableCollections).
  2. Enable categories one at a time as you refactor — the category checkbox toggles the whole group.
  3. Use @Suppress("<RuleId>") for individual, intentional exceptions.

Requirements & Compatibility

  • IntelliJ IDEA 2024.2+ or Android Studio Ladybug (2024.2)+
  • The bundled Kotlin plugin (enabled by default)
ComposeGuardSupported IDE builds
1.2.x2024.2 – 2026.2

Contributing

Contributions are welcome — issues and pull requests both.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/amazing-feature.
  3. Make your change and add tests (./gradlew :compose-guard:test).
  4. Commit and push, then open a Pull Request.

Credits

Built on the excellent Compose Rules guidelines by Nacho Lopez (mrmans0n).

Find this repository useful? ❤️

Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🤩

License

Designed and developed by 2025 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Made with ❤️ by androidpoet

About

Real-time detection of Jetpack Compose best practices and rule violations directly in Android Studio.

Topics

Resources

Stars

115 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

ComposeGuard

JetBrains PluginDownloadsLicenseProfileAndroid Weekly

Catch Jetpack Compose mistakes as you type — 39 best-practice rules from the Compose Rules guidelines, surfaced live in Android Studio & IntelliJ IDEA with inline highlights, gutter icons, and one-click fixes.


Preview

ComposeGuard Preview

Why ComposeGuard?

The Compose Rules catch the subtle mistakes that hurt Compose code — missing Modifier parameters, un-remembered state, unstable collections, reused modifiers, and dozens more. ComposeGuard brings those checks into the editor, so you fix them while the code is still fresh instead of discovering them in a build log or a code review.

  • Instant — analysis runs as you type; no build, no Gradle task, no CI round-trip.
  • 🎯 Accurate — rules are PSI-based and tuned to avoid false positives on valid patterns (overrides, scoped slots, mutually-exclusive branches, run-once effects, …).
  • 🛠 Actionable — most violations come with a quick fix (Alt+Enter) and a detailed explanation of why it matters.
  • 🎚 Configurable — enable/disable any rule or whole category, or suppress per declaration.

Table of Contents

Features

  • Real-time highlighting — violations appear as colored underlines while you edit.
  • Gutter icons — a color-coded dot per @Composable summarizes its status at a glance:
    • 🔴 Error 🟠 Warning ⚪ Weak warning 🔵 Info
  • Inline hints — compact badges next to function names show rule violations.
  • Hover tooltips — every violation explains the problem, the reasoning, and the fix.
  • Quick fixes — rename, add a modifier parameter, wrap in remember, switch to a type-specific state, make a preview private, swap to an immutable collection, and more.
  • 39 rules across 6 categories — see the full Rule Reference.

Installation

Install from Marketplace

  1. Open Android Studio or IntelliJ IDEA.
  2. Go to SettingsPluginsMarketplace.
  3. Search for ComposeGuard.
  4. Click Install and restart when prompted.

Or install directly from the JetBrains Marketplace.

Quick Start

Once installed, ComposeGuard automatically analyzes any Kotlin file containing @Composable functions — no configuration required. Here are a few things it catches:

// 🟠 Naming: Unit-returning composables should be PascalCase
@Composable
funuserCard(user:User) { } // → rename to "UserCard"// 🟠 Modifier: public UI composables should expose a Modifier
@Composable
funProductCard(product:Product) { // → add `modifier: Modifier = Modifier`Column { Text(product.name) }
}
// 🔴 State: state must be remembered
@Composable
funCounter() {
val count = mutableStateOf(0) // → wrap in remember { }
}
// 🟠 Stricter: prefer stable collections
@Composable
funItemList(items:List<Item>) { } // → use ImmutableList<Item>

Hover any highlight for the full explanation, or press Alt+Enter to apply a fix.

Suppressing Rules

To intentionally allow a violation, annotate the declaration with @Suppress using the rule id (the same id shown in the warning, e.g. ModifierRequired). The quick fix can insert this for you:

@Suppress("ModifierRequired")
@Composable
funSplashLogo() {
Image(painterResource(R.drawable.logo), contentDescription =null)
}
// Multiple rules at once:
@Suppress("ModifierRequired", "ComposableNaming")
@Composable
funsplash() { /* ... */ }

Suppression works at the function, property, or class level. To turn rules off project-wide instead, use Configuration.

Rule Reference

ComposeGuard ships 39 rules based on the Compose Rules guidelines. Severity legend: 🔴 Error · 🟠 Warning · ⚪ Weak warning · 🔵 Info.

Naming

Rule idChecksSeverity
ComposableNamingUnit-returning composables use PascalCase; value-returning use camelCase🟠
CompositionLocalNamingCompositionLocal properties are prefixed with Local🟠
PreviewNaming@Preview functions reference Preview in their name
MultipreviewNamingMultipreview annotation classes reference Preview
ComposableAnnotationNaming@ComposableTargetMarker annotations end with Composable
EventParameterNamingEvent lambdas use present tense (onClick, not onClicked)

Modifiers

Rule idChecksSeverity
ModifierRequiredPublic, UI-emitting composables expose a Modifier parameter🟠
ModifierDefaultValuemodifier parameters default to Modifier🟠
ModifierNamingThe main modifier is named modifier; others follow xModifier
ModifierTopMostThe modifier is applied to the root-most layout🟠
ModifierReuseThe same modifier isn't applied to multiple live nodes🟠
ModifierOrderModifier chain order is intentional (e.g. padding before clickable)🟠
AvoidComposedPrefer Modifier.Node over the deprecated composed { } factory🟠

State

Rule idChecksSeverity
RememberStatemutableStateOf and friends are wrapped in remember { }🔴
TypeSpecificStatePrimitives use mutableIntStateOf / mutableFloatStateOf / …🟠
DerivedStateOfCandidateValues computed from state use derivedStateOf🟠
FrequentRecompositionHot observable sources use lifecycle-aware collection🟠
DeferStateReadsFast-changing state reads are deferred to lambda modifiers🟠
HoistStateState is hoisted to the appropriate level🔵
MutableStateParameterPass value + callback instead of a MutableState parameter🟠

Parameters

Rule idChecksSeverity
ParameterOrderingOrder is required → modifier → optional → trailing content
TrailingLambdaThe content slot is the trailing lambda; event handlers are not
MutableParameterAvoid inherently mutable types (MutableList, ArrayList, …) as parameters🟠
ExplicitDependenciesMake injected ViewModels explicit parameters
ViewModelForwardingDon't forward a ViewModel into another composable🟠

Composables & Effects

Rule idChecksSeverity
ContentEmissionA composable emits content or returns a value, not both🟠
MultipleContentEmittersA composable emits a single piece of content🟠
ContentSlotReusedA content slot isn't invoked more than once on the same pass🟠
EffectKeysChanging captured values are passed as effect keys🟠
LambdaParameterInEffectLambda parameters used in effects are wrapped in rememberUpdatedState🟠
MovableContentmovableContentOf is remembered🔴
PreviewVisibility@Preview composables are private🟠
ComponentDefaultsVisibilityA <Component>Defaults object matches its composable's visibility🟠
LazyListMissingKeyLazy list items provide a stable key🔵
ComposableNestingDepthComposables are not nested deeper than the configured limit (opt-in)
LazyListContentTypeHeterogeneous lazy lists set a contentType🔵

Stricter

Rule idChecksSeverity
UnstableCollectionsPrefer ImmutableList / PersistentList over List, Set, Map🟠
CompositionLocalAllowlistCustom CompositionLocals are declared only when allowlisted (opt-in)🟠
Material2UsageMigrate androidx.compose.material (M2) imports to Material 3🔵

Suppress any rule with @Suppress("<RuleId>"), or toggle it in Settings → Tools → ComposeGuard.

Statistics Dashboard

ComposeGuard includes a tool window that tracks rule violations across your project.

ComposeGuard Statistics Dashboard

  • On-demand project scan — press Scan Project to count violations across every Kotlin file.
  • Category breakdown — see violations grouped by rule category.
  • Rule-level details — drill into specific rules.
  • Project overview — track overall code-quality trends.
  • Export — save the last scan as JSON or SARIF for CI dashboards and code-scanning tools.

Open it from ViewTool WindowsComposeGuard, or click a ComposeGuard gutter icon.

Configuration

Configure ComposeGuard at SettingsToolsComposeGuard.

ComposeGuard Settings - Disable Rules

  • Enable All Rules — master switch that selects or clears every rule at once.
  • Display options — toggle gutter icons and inlay hints.
  • Rule configuration — enable/disable individual rules or entire categories.
  • Analyze test sources — uncheck to leave test source roots alone.

Project configuration (.editorconfig)

ComposeGuard reads the same compose_* keys that the upstream Compose Rules ktlint ruleset uses, so a team can commit one .editorconfig and share it between the IDE plugin and CI. Keys are read from the nearest .editorconfig (walking up to the one marked root = true) in any section that applies to Kotlin files, for example [*.{kt,kts}]. Lists are comma-separated; entries containing regex metacharacters are treated as regular expressions.

KeyAffectsMeaning
compose_allowed_composable_function_namesComposableNamingNames (or regexes) exempt from the PascalCase/camelCase check
compose_content_emittersContentEmission, MultipleContentEmitters, ModifierRequired, TrailingLambdaExtra composables that count as emitting UI
compose_content_emitters_denylistsame as aboveComposables that must never count as emitting UI
compose_check_modifiers_for_visibilityModifierRequiredonly_public (default), public_and_internal, or all
compose_modifier_missing_ignore_annotatedModifierRequiredAnnotation names whose composables are skipped
compose_custom_modifiersModifierRequired, ModifierNaming, ModifierDefaultValue, ParameterOrdering, TrailingLambdaExtra types treated as Modifier (e.g. GlanceModifier)
compose_treat_as_lambdaParameterOrdering, TrailingLambdaType aliases treated as plain lambdas
compose_treat_as_composable_lambdaParameterOrdering, TrailingLambdaType aliases treated as @Composable content slots
compose_view_model_factoriesExplicitDependenciesExtra ViewModel factory functions
compose_allowed_composition_localsExplicitDependencies, CompositionLocalAllowlistCompositionLocals that may be read or declared
compose_allowed_state_holder_namesViewModelForwardingType name regexes that are not treated as forwarded ViewModels
compose_allowed_forwardingViewModelForwardingComposables a ViewModel may be forwarded to
compose_allowed_forwarding_of_typesViewModelForwardingViewModel types that may be forwarded
compose_allowed_from_m2Material2UsageMaterial 2 imports (or package prefixes) that are allowed
compose_allowed_lambda_parameter_namesEventParameterNamingEvent parameter names exempt from the present-tense check
compose_preview_naming_strategyPreviewNaminganywhere (default), suffix, or prefix
compose_composable_nesting_depth_thresholdComposableNestingDepthMaximum nesting depth (default 3)
compose_disallow_material2, compose_disallow_unstable_collections, compose_preview_naming_enabled, compose_composable_nesting_depth_enabledrule enablementtrue turns the rule on for this project regardless of IDE settings
root = true
[*.{kt,kts}]compose_allowed_from_m2 = androidx.compose.material.icons
compose_treat_as_composable_lambda = Slot
compose_composable_nesting_depth_enabled = true
compose_composable_nesting_depth_threshold = 4

Adopting ComposeGuard in an existing codebase

Adding the plugin to a large legacy project? Roll it out gradually instead of facing every warning at once:

  1. Start with the Stricter category off (Material2Usage, UnstableCollections).
  2. Enable categories one at a time as you refactor — the category checkbox toggles the whole group.
  3. Use @Suppress("<RuleId>") for individual, intentional exceptions.

Requirements & Compatibility

  • IntelliJ IDEA 2024.2+ or Android Studio Ladybug (2024.2)+
  • The bundled Kotlin plugin (enabled by default)
ComposeGuardSupported IDE builds
1.2.x2024.2 – 2026.2

Contributing

Contributions are welcome — issues and pull requests both.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/amazing-feature.
  3. Make your change and add tests (./gradlew :compose-guard:test).
  4. Commit and push, then open a Pull Request.

Credits

Built on the excellent Compose Rules guidelines by Nacho Lopez (mrmans0n).

Find this repository useful? ❤️

Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🤩

License

Designed and developed by 2025 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Made with ❤️ by androidpoet

About

Real-time detection of Jetpack Compose best practices and rule violations directly in Android Studio.

Topics

Resources

Stars

115 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

ComposeGuard

JetBrains PluginDownloadsLicenseProfileAndroid Weekly

Catch Jetpack Compose mistakes as you type — 39 best-practice rules from the Compose Rules guidelines, surfaced live in Android Studio & IntelliJ IDEA with inline highlights, gutter icons, and one-click fixes.


Preview

ComposeGuard Preview

Why ComposeGuard?

The Compose Rules catch the subtle mistakes that hurt Compose code — missing Modifier parameters, un-remembered state, unstable collections, reused modifiers, and dozens more. ComposeGuard brings those checks into the editor, so you fix them while the code is still fresh instead of discovering them in a build log or a code review.

  • Instant — analysis runs as you type; no build, no Gradle task, no CI round-trip.
  • 🎯 Accurate — rules are PSI-based and tuned to avoid false positives on valid patterns (overrides, scoped slots, mutually-exclusive branches, run-once effects, …).
  • 🛠 Actionable — most violations come with a quick fix (Alt+Enter) and a detailed explanation of why it matters.
  • 🎚 Configurable — enable/disable any rule or whole category, or suppress per declaration.

Table of Contents

Features

  • Real-time highlighting — violations appear as colored underlines while you edit.
  • Gutter icons — a color-coded dot per @Composable summarizes its status at a glance:
    • 🔴 Error 🟠 Warning ⚪ Weak warning 🔵 Info
  • Inline hints — compact badges next to function names show rule violations.
  • Hover tooltips — every violation explains the problem, the reasoning, and the fix.
  • Quick fixes — rename, add a modifier parameter, wrap in remember, switch to a type-specific state, make a preview private, swap to an immutable collection, and more.
  • 39 rules across 6 categories — see the full Rule Reference.

Installation

Install from Marketplace

  1. Open Android Studio or IntelliJ IDEA.
  2. Go to SettingsPluginsMarketplace.
  3. Search for ComposeGuard.
  4. Click Install and restart when prompted.

Or install directly from the JetBrains Marketplace.

Quick Start

Once installed, ComposeGuard automatically analyzes any Kotlin file containing @Composable functions — no configuration required. Here are a few things it catches:

// 🟠 Naming: Unit-returning composables should be PascalCase
@Composable
funuserCard(user:User) { } // → rename to "UserCard"// 🟠 Modifier: public UI composables should expose a Modifier
@Composable
funProductCard(product:Product) { // → add `modifier: Modifier = Modifier`Column { Text(product.name) }
}
// 🔴 State: state must be remembered
@Composable
funCounter() {
val count = mutableStateOf(0) // → wrap in remember { }
}
// 🟠 Stricter: prefer stable collections
@Composable
funItemList(items:List<Item>) { } // → use ImmutableList<Item>

Hover any highlight for the full explanation, or press Alt+Enter to apply a fix.

Suppressing Rules

To intentionally allow a violation, annotate the declaration with @Suppress using the rule id (the same id shown in the warning, e.g. ModifierRequired). The quick fix can insert this for you:

@Suppress("ModifierRequired")
@Composable
funSplashLogo() {
Image(painterResource(R.drawable.logo), contentDescription =null)
}
// Multiple rules at once:
@Suppress("ModifierRequired", "ComposableNaming")
@Composable
funsplash() { /* ... */ }

Suppression works at the function, property, or class level. To turn rules off project-wide instead, use Configuration.

Rule Reference

ComposeGuard ships 39 rules based on the Compose Rules guidelines. Severity legend: 🔴 Error · 🟠 Warning · ⚪ Weak warning · 🔵 Info.

Naming

Rule idChecksSeverity
ComposableNamingUnit-returning composables use PascalCase; value-returning use camelCase🟠
CompositionLocalNamingCompositionLocal properties are prefixed with Local🟠
PreviewNaming@Preview functions reference Preview in their name
MultipreviewNamingMultipreview annotation classes reference Preview
ComposableAnnotationNaming@ComposableTargetMarker annotations end with Composable
EventParameterNamingEvent lambdas use present tense (onClick, not onClicked)

Modifiers

Rule idChecksSeverity
ModifierRequiredPublic, UI-emitting composables expose a Modifier parameter🟠
ModifierDefaultValuemodifier parameters default to Modifier🟠
ModifierNamingThe main modifier is named modifier; others follow xModifier
ModifierTopMostThe modifier is applied to the root-most layout🟠
ModifierReuseThe same modifier isn't applied to multiple live nodes🟠
ModifierOrderModifier chain order is intentional (e.g. padding before clickable)🟠
AvoidComposedPrefer Modifier.Node over the deprecated composed { } factory🟠

State

Rule idChecksSeverity
RememberStatemutableStateOf and friends are wrapped in remember { }🔴
TypeSpecificStatePrimitives use mutableIntStateOf / mutableFloatStateOf / …🟠
DerivedStateOfCandidateValues computed from state use derivedStateOf🟠
FrequentRecompositionHot observable sources use lifecycle-aware collection🟠
DeferStateReadsFast-changing state reads are deferred to lambda modifiers🟠
HoistStateState is hoisted to the appropriate level🔵
MutableStateParameterPass value + callback instead of a MutableState parameter🟠

Parameters

Rule idChecksSeverity
ParameterOrderingOrder is required → modifier → optional → trailing content
TrailingLambdaThe content slot is the trailing lambda; event handlers are not
MutableParameterAvoid inherently mutable types (MutableList, ArrayList, …) as parameters🟠
ExplicitDependenciesMake injected ViewModels explicit parameters
ViewModelForwardingDon't forward a ViewModel into another composable🟠

Composables & Effects

Rule idChecksSeverity
ContentEmissionA composable emits content or returns a value, not both🟠
MultipleContentEmittersA composable emits a single piece of content🟠
ContentSlotReusedA content slot isn't invoked more than once on the same pass🟠
EffectKeysChanging captured values are passed as effect keys🟠
LambdaParameterInEffectLambda parameters used in effects are wrapped in rememberUpdatedState🟠
MovableContentmovableContentOf is remembered🔴
PreviewVisibility@Preview composables are private🟠
ComponentDefaultsVisibilityA <Component>Defaults object matches its composable's visibility🟠
LazyListMissingKeyLazy list items provide a stable key🔵
ComposableNestingDepthComposables are not nested deeper than the configured limit (opt-in)
LazyListContentTypeHeterogeneous lazy lists set a contentType🔵

Stricter

Rule idChecksSeverity
UnstableCollectionsPrefer ImmutableList / PersistentList over List, Set, Map🟠
CompositionLocalAllowlistCustom CompositionLocals are declared only when allowlisted (opt-in)🟠
Material2UsageMigrate androidx.compose.material (M2) imports to Material 3🔵

Suppress any rule with @Suppress("<RuleId>"), or toggle it in Settings → Tools → ComposeGuard.

Statistics Dashboard

ComposeGuard includes a tool window that tracks rule violations across your project.

ComposeGuard Statistics Dashboard

  • On-demand project scan — press Scan Project to count violations across every Kotlin file.
  • Category breakdown — see violations grouped by rule category.
  • Rule-level details — drill into specific rules.
  • Project overview — track overall code-quality trends.
  • Export — save the last scan as JSON or SARIF for CI dashboards and code-scanning tools.

Open it from ViewTool WindowsComposeGuard, or click a ComposeGuard gutter icon.

Configuration

Configure ComposeGuard at SettingsToolsComposeGuard.

ComposeGuard Settings - Disable Rules

  • Enable All Rules — master switch that selects or clears every rule at once.
  • Display options — toggle gutter icons and inlay hints.
  • Rule configuration — enable/disable individual rules or entire categories.
  • Analyze test sources — uncheck to leave test source roots alone.

Project configuration (.editorconfig)

ComposeGuard reads the same compose_* keys that the upstream Compose Rules ktlint ruleset uses, so a team can commit one .editorconfig and share it between the IDE plugin and CI. Keys are read from the nearest .editorconfig (walking up to the one marked root = true) in any section that applies to Kotlin files, for example [*.{kt,kts}]. Lists are comma-separated; entries containing regex metacharacters are treated as regular expressions.

KeyAffectsMeaning
compose_allowed_composable_function_namesComposableNamingNames (or regexes) exempt from the PascalCase/camelCase check
compose_content_emittersContentEmission, MultipleContentEmitters, ModifierRequired, TrailingLambdaExtra composables that count as emitting UI
compose_content_emitters_denylistsame as aboveComposables that must never count as emitting UI
compose_check_modifiers_for_visibilityModifierRequiredonly_public (default), public_and_internal, or all
compose_modifier_missing_ignore_annotatedModifierRequiredAnnotation names whose composables are skipped
compose_custom_modifiersModifierRequired, ModifierNaming, ModifierDefaultValue, ParameterOrdering, TrailingLambdaExtra types treated as Modifier (e.g. GlanceModifier)
compose_treat_as_lambdaParameterOrdering, TrailingLambdaType aliases treated as plain lambdas
compose_treat_as_composable_lambdaParameterOrdering, TrailingLambdaType aliases treated as @Composable content slots
compose_view_model_factoriesExplicitDependenciesExtra ViewModel factory functions
compose_allowed_composition_localsExplicitDependencies, CompositionLocalAllowlistCompositionLocals that may be read or declared
compose_allowed_state_holder_namesViewModelForwardingType name regexes that are not treated as forwarded ViewModels
compose_allowed_forwardingViewModelForwardingComposables a ViewModel may be forwarded to
compose_allowed_forwarding_of_typesViewModelForwardingViewModel types that may be forwarded
compose_allowed_from_m2Material2UsageMaterial 2 imports (or package prefixes) that are allowed
compose_allowed_lambda_parameter_namesEventParameterNamingEvent parameter names exempt from the present-tense check
compose_preview_naming_strategyPreviewNaminganywhere (default), suffix, or prefix
compose_composable_nesting_depth_thresholdComposableNestingDepthMaximum nesting depth (default 3)
compose_disallow_material2, compose_disallow_unstable_collections, compose_preview_naming_enabled, compose_composable_nesting_depth_enabledrule enablementtrue turns the rule on for this project regardless of IDE settings
root = true
[*.{kt,kts}]compose_allowed_from_m2 = androidx.compose.material.icons
compose_treat_as_composable_lambda = Slot
compose_composable_nesting_depth_enabled = true
compose_composable_nesting_depth_threshold = 4

Adopting ComposeGuard in an existing codebase

Adding the plugin to a large legacy project? Roll it out gradually instead of facing every warning at once:

  1. Start with the Stricter category off (Material2Usage, UnstableCollections).
  2. Enable categories one at a time as you refactor — the category checkbox toggles the whole group.
  3. Use @Suppress("<RuleId>") for individual, intentional exceptions.

Requirements & Compatibility

  • IntelliJ IDEA 2024.2+ or Android Studio Ladybug (2024.2)+
  • The bundled Kotlin plugin (enabled by default)
ComposeGuardSupported IDE builds
1.2.x2024.2 – 2026.2

Contributing

Contributions are welcome — issues and pull requests both.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/amazing-feature.
  3. Make your change and add tests (./gradlew :compose-guard:test).
  4. Commit and push, then open a Pull Request.

Credits

Built on the excellent Compose Rules guidelines by Nacho Lopez (mrmans0n).

Find this repository useful? ❤️

Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🤩

License

Designed and developed by 2025 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Made with ❤️ by androidpoet

About

Real-time detection of Jetpack Compose best practices and rule violations directly in Android Studio.

Topics

Resources

Stars

115 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

ComposeGuard

JetBrains PluginDownloadsLicenseProfileAndroid Weekly

Catch Jetpack Compose mistakes as you type — 39 best-practice rules from the Compose Rules guidelines, surfaced live in Android Studio & IntelliJ IDEA with inline highlights, gutter icons, and one-click fixes.


Preview

ComposeGuard Preview

Why ComposeGuard?

The Compose Rules catch the subtle mistakes that hurt Compose code — missing Modifier parameters, un-remembered state, unstable collections, reused modifiers, and dozens more. ComposeGuard brings those checks into the editor, so you fix them while the code is still fresh instead of discovering them in a build log or a code review.

  • Instant — analysis runs as you type; no build, no Gradle task, no CI round-trip.
  • 🎯 Accurate — rules are PSI-based and tuned to avoid false positives on valid patterns (overrides, scoped slots, mutually-exclusive branches, run-once effects, …).
  • 🛠 Actionable — most violations come with a quick fix (Alt+Enter) and a detailed explanation of why it matters.
  • 🎚 Configurable — enable/disable any rule or whole category, or suppress per declaration.

Table of Contents

Features

  • Real-time highlighting — violations appear as colored underlines while you edit.
  • Gutter icons — a color-coded dot per @Composable summarizes its status at a glance:
    • 🔴 Error 🟠 Warning ⚪ Weak warning 🔵 Info
  • Inline hints — compact badges next to function names show rule violations.
  • Hover tooltips — every violation explains the problem, the reasoning, and the fix.
  • Quick fixes — rename, add a modifier parameter, wrap in remember, switch to a type-specific state, make a preview private, swap to an immutable collection, and more.
  • 39 rules across 6 categories — see the full Rule Reference.

Installation

Install from Marketplace

  1. Open Android Studio or IntelliJ IDEA.
  2. Go to SettingsPluginsMarketplace.
  3. Search for ComposeGuard.
  4. Click Install and restart when prompted.

Or install directly from the JetBrains Marketplace.

Quick Start

Once installed, ComposeGuard automatically analyzes any Kotlin file containing @Composable functions — no configuration required. Here are a few things it catches:

// 🟠 Naming: Unit-returning composables should be PascalCase
@Composable
funuserCard(user:User) { } // → rename to "UserCard"// 🟠 Modifier: public UI composables should expose a Modifier
@Composable
funProductCard(product:Product) { // → add `modifier: Modifier = Modifier`Column { Text(product.name) }
}
// 🔴 State: state must be remembered
@Composable
funCounter() {
val count = mutableStateOf(0) // → wrap in remember { }
}
// 🟠 Stricter: prefer stable collections
@Composable
funItemList(items:List<Item>) { } // → use ImmutableList<Item>

Hover any highlight for the full explanation, or press Alt+Enter to apply a fix.

Suppressing Rules

To intentionally allow a violation, annotate the declaration with @Suppress using the rule id (the same id shown in the warning, e.g. ModifierRequired). The quick fix can insert this for you:

@Suppress("ModifierRequired")
@Composable
funSplashLogo() {
Image(painterResource(R.drawable.logo), contentDescription =null)
}
// Multiple rules at once:
@Suppress("ModifierRequired", "ComposableNaming")
@Composable
funsplash() { /* ... */ }

Suppression works at the function, property, or class level. To turn rules off project-wide instead, use Configuration.

Rule Reference

ComposeGuard ships 39 rules based on the Compose Rules guidelines. Severity legend: 🔴 Error · 🟠 Warning · ⚪ Weak warning · 🔵 Info.

Naming

Rule idChecksSeverity
ComposableNamingUnit-returning composables use PascalCase; value-returning use camelCase🟠
CompositionLocalNamingCompositionLocal properties are prefixed with Local🟠
PreviewNaming@Preview functions reference Preview in their name
MultipreviewNamingMultipreview annotation classes reference Preview
ComposableAnnotationNaming@ComposableTargetMarker annotations end with Composable
EventParameterNamingEvent lambdas use present tense (onClick, not onClicked)

Modifiers

Rule idChecksSeverity
ModifierRequiredPublic, UI-emitting composables expose a Modifier parameter🟠
ModifierDefaultValuemodifier parameters default to Modifier🟠
ModifierNamingThe main modifier is named modifier; others follow xModifier
ModifierTopMostThe modifier is applied to the root-most layout🟠
ModifierReuseThe same modifier isn't applied to multiple live nodes🟠
ModifierOrderModifier chain order is intentional (e.g. padding before clickable)🟠
AvoidComposedPrefer Modifier.Node over the deprecated composed { } factory🟠

State

Rule idChecksSeverity
RememberStatemutableStateOf and friends are wrapped in remember { }🔴
TypeSpecificStatePrimitives use mutableIntStateOf / mutableFloatStateOf / …🟠
DerivedStateOfCandidateValues computed from state use derivedStateOf🟠
FrequentRecompositionHot observable sources use lifecycle-aware collection🟠
DeferStateReadsFast-changing state reads are deferred to lambda modifiers🟠
HoistStateState is hoisted to the appropriate level🔵
MutableStateParameterPass value + callback instead of a MutableState parameter🟠

Parameters

Rule idChecksSeverity
ParameterOrderingOrder is required → modifier → optional → trailing content
TrailingLambdaThe content slot is the trailing lambda; event handlers are not
MutableParameterAvoid inherently mutable types (MutableList, ArrayList, …) as parameters🟠
ExplicitDependenciesMake injected ViewModels explicit parameters
ViewModelForwardingDon't forward a ViewModel into another composable🟠

Composables & Effects

Rule idChecksSeverity
ContentEmissionA composable emits content or returns a value, not both🟠
MultipleContentEmittersA composable emits a single piece of content🟠
ContentSlotReusedA content slot isn't invoked more than once on the same pass🟠
EffectKeysChanging captured values are passed as effect keys🟠
LambdaParameterInEffectLambda parameters used in effects are wrapped in rememberUpdatedState🟠
MovableContentmovableContentOf is remembered🔴
PreviewVisibility@Preview composables are private🟠
ComponentDefaultsVisibilityA <Component>Defaults object matches its composable's visibility🟠
LazyListMissingKeyLazy list items provide a stable key🔵
ComposableNestingDepthComposables are not nested deeper than the configured limit (opt-in)
LazyListContentTypeHeterogeneous lazy lists set a contentType🔵

Stricter

Rule idChecksSeverity
UnstableCollectionsPrefer ImmutableList / PersistentList over List, Set, Map🟠
CompositionLocalAllowlistCustom CompositionLocals are declared only when allowlisted (opt-in)🟠
Material2UsageMigrate androidx.compose.material (M2) imports to Material 3🔵

Suppress any rule with @Suppress("<RuleId>"), or toggle it in Settings → Tools → ComposeGuard.

Statistics Dashboard

ComposeGuard includes a tool window that tracks rule violations across your project.

ComposeGuard Statistics Dashboard

  • On-demand project scan — press Scan Project to count violations across every Kotlin file.
  • Category breakdown — see violations grouped by rule category.
  • Rule-level details — drill into specific rules.
  • Project overview — track overall code-quality trends.
  • Export — save the last scan as JSON or SARIF for CI dashboards and code-scanning tools.

Open it from ViewTool WindowsComposeGuard, or click a ComposeGuard gutter icon.

Configuration

Configure ComposeGuard at SettingsToolsComposeGuard.

ComposeGuard Settings - Disable Rules

  • Enable All Rules — master switch that selects or clears every rule at once.
  • Display options — toggle gutter icons and inlay hints.
  • Rule configuration — enable/disable individual rules or entire categories.
  • Analyze test sources — uncheck to leave test source roots alone.

Project configuration (.editorconfig)

ComposeGuard reads the same compose_* keys that the upstream Compose Rules ktlint ruleset uses, so a team can commit one .editorconfig and share it between the IDE plugin and CI. Keys are read from the nearest .editorconfig (walking up to the one marked root = true) in any section that applies to Kotlin files, for example [*.{kt,kts}]. Lists are comma-separated; entries containing regex metacharacters are treated as regular expressions.

KeyAffectsMeaning
compose_allowed_composable_function_namesComposableNamingNames (or regexes) exempt from the PascalCase/camelCase check
compose_content_emittersContentEmission, MultipleContentEmitters, ModifierRequired, TrailingLambdaExtra composables that count as emitting UI
compose_content_emitters_denylistsame as aboveComposables that must never count as emitting UI
compose_check_modifiers_for_visibilityModifierRequiredonly_public (default), public_and_internal, or all
compose_modifier_missing_ignore_annotatedModifierRequiredAnnotation names whose composables are skipped
compose_custom_modifiersModifierRequired, ModifierNaming, ModifierDefaultValue, ParameterOrdering, TrailingLambdaExtra types treated as Modifier (e.g. GlanceModifier)
compose_treat_as_lambdaParameterOrdering, TrailingLambdaType aliases treated as plain lambdas
compose_treat_as_composable_lambdaParameterOrdering, TrailingLambdaType aliases treated as @Composable content slots
compose_view_model_factoriesExplicitDependenciesExtra ViewModel factory functions
compose_allowed_composition_localsExplicitDependencies, CompositionLocalAllowlistCompositionLocals that may be read or declared
compose_allowed_state_holder_namesViewModelForwardingType name regexes that are not treated as forwarded ViewModels
compose_allowed_forwardingViewModelForwardingComposables a ViewModel may be forwarded to
compose_allowed_forwarding_of_typesViewModelForwardingViewModel types that may be forwarded
compose_allowed_from_m2Material2UsageMaterial 2 imports (or package prefixes) that are allowed
compose_allowed_lambda_parameter_namesEventParameterNamingEvent parameter names exempt from the present-tense check
compose_preview_naming_strategyPreviewNaminganywhere (default), suffix, or prefix
compose_composable_nesting_depth_thresholdComposableNestingDepthMaximum nesting depth (default 3)
compose_disallow_material2, compose_disallow_unstable_collections, compose_preview_naming_enabled, compose_composable_nesting_depth_enabledrule enablementtrue turns the rule on for this project regardless of IDE settings
root = true
[*.{kt,kts}]compose_allowed_from_m2 = androidx.compose.material.icons
compose_treat_as_composable_lambda = Slot
compose_composable_nesting_depth_enabled = true
compose_composable_nesting_depth_threshold = 4

Adopting ComposeGuard in an existing codebase

Adding the plugin to a large legacy project? Roll it out gradually instead of facing every warning at once:

  1. Start with the Stricter category off (Material2Usage, UnstableCollections).
  2. Enable categories one at a time as you refactor — the category checkbox toggles the whole group.
  3. Use @Suppress("<RuleId>") for individual, intentional exceptions.

Requirements & Compatibility

  • IntelliJ IDEA 2024.2+ or Android Studio Ladybug (2024.2)+
  • The bundled Kotlin plugin (enabled by default)
ComposeGuardSupported IDE builds
1.2.x2024.2 – 2026.2

Contributing

Contributions are welcome — issues and pull requests both.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/amazing-feature.
  3. Make your change and add tests (./gradlew :compose-guard:test).
  4. Commit and push, then open a Pull Request.

Credits

Built on the excellent Compose Rules guidelines by Nacho Lopez (mrmans0n).

Find this repository useful? ❤️

Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🤩

License

Designed and developed by 2025 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Made with ❤️ by androidpoet

About

Real-time detection of Jetpack Compose best practices and rule violations directly in Android Studio.

Topics

Resources

Stars

115 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

ComposeGuard

JetBrains PluginDownloadsLicenseProfileAndroid Weekly

Catch Jetpack Compose mistakes as you type — 39 best-practice rules from the Compose Rules guidelines, surfaced live in Android Studio & IntelliJ IDEA with inline highlights, gutter icons, and one-click fixes.


Preview

ComposeGuard Preview

Why ComposeGuard?

The Compose Rules catch the subtle mistakes that hurt Compose code — missing Modifier parameters, un-remembered state, unstable collections, reused modifiers, and dozens more. ComposeGuard brings those checks into the editor, so you fix them while the code is still fresh instead of discovering them in a build log or a code review.

  • Instant — analysis runs as you type; no build, no Gradle task, no CI round-trip.
  • 🎯 Accurate — rules are PSI-based and tuned to avoid false positives on valid patterns (overrides, scoped slots, mutually-exclusive branches, run-once effects, …).
  • 🛠 Actionable — most violations come with a quick fix (Alt+Enter) and a detailed explanation of why it matters.
  • 🎚 Configurable — enable/disable any rule or whole category, or suppress per declaration.

Table of Contents

Features

  • Real-time highlighting — violations appear as colored underlines while you edit.
  • Gutter icons — a color-coded dot per @Composable summarizes its status at a glance:
    • 🔴 Error 🟠 Warning ⚪ Weak warning 🔵 Info
  • Inline hints — compact badges next to function names show rule violations.
  • Hover tooltips — every violation explains the problem, the reasoning, and the fix.
  • Quick fixes — rename, add a modifier parameter, wrap in remember, switch to a type-specific state, make a preview private, swap to an immutable collection, and more.
  • 39 rules across 6 categories — see the full Rule Reference.

Installation

Install from Marketplace

  1. Open Android Studio or IntelliJ IDEA.
  2. Go to SettingsPluginsMarketplace.
  3. Search for ComposeGuard.
  4. Click Install and restart when prompted.

Or install directly from the JetBrains Marketplace.

Quick Start

Once installed, ComposeGuard automatically analyzes any Kotlin file containing @Composable functions — no configuration required. Here are a few things it catches:

// 🟠 Naming: Unit-returning composables should be PascalCase
@Composable
funuserCard(user:User) { } // → rename to "UserCard"// 🟠 Modifier: public UI composables should expose a Modifier
@Composable
funProductCard(product:Product) { // → add `modifier: Modifier = Modifier`Column { Text(product.name) }
}
// 🔴 State: state must be remembered
@Composable
funCounter() {
val count = mutableStateOf(0) // → wrap in remember { }
}
// 🟠 Stricter: prefer stable collections
@Composable
funItemList(items:List<Item>) { } // → use ImmutableList<Item>

Hover any highlight for the full explanation, or press Alt+Enter to apply a fix.

Suppressing Rules

To intentionally allow a violation, annotate the declaration with @Suppress using the rule id (the same id shown in the warning, e.g. ModifierRequired). The quick fix can insert this for you:

@Suppress("ModifierRequired")
@Composable
funSplashLogo() {
Image(painterResource(R.drawable.logo), contentDescription =null)
}
// Multiple rules at once:
@Suppress("ModifierRequired", "ComposableNaming")
@Composable
funsplash() { /* ... */ }

Suppression works at the function, property, or class level. To turn rules off project-wide instead, use Configuration.

Rule Reference

ComposeGuard ships 39 rules based on the Compose Rules guidelines. Severity legend: 🔴 Error · 🟠 Warning · ⚪ Weak warning · 🔵 Info.

Naming

Rule idChecksSeverity
ComposableNamingUnit-returning composables use PascalCase; value-returning use camelCase🟠
CompositionLocalNamingCompositionLocal properties are prefixed with Local🟠
PreviewNaming@Preview functions reference Preview in their name
MultipreviewNamingMultipreview annotation classes reference Preview
ComposableAnnotationNaming@ComposableTargetMarker annotations end with Composable
EventParameterNamingEvent lambdas use present tense (onClick, not onClicked)

Modifiers

Rule idChecksSeverity
ModifierRequiredPublic, UI-emitting composables expose a Modifier parameter🟠
ModifierDefaultValuemodifier parameters default to Modifier🟠
ModifierNamingThe main modifier is named modifier; others follow xModifier
ModifierTopMostThe modifier is applied to the root-most layout🟠
ModifierReuseThe same modifier isn't applied to multiple live nodes🟠
ModifierOrderModifier chain order is intentional (e.g. padding before clickable)🟠
AvoidComposedPrefer Modifier.Node over the deprecated composed { } factory🟠

State

Rule idChecksSeverity
RememberStatemutableStateOf and friends are wrapped in remember { }🔴
TypeSpecificStatePrimitives use mutableIntStateOf / mutableFloatStateOf / …🟠
DerivedStateOfCandidateValues computed from state use derivedStateOf🟠
FrequentRecompositionHot observable sources use lifecycle-aware collection🟠
DeferStateReadsFast-changing state reads are deferred to lambda modifiers🟠
HoistStateState is hoisted to the appropriate level🔵
MutableStateParameterPass value + callback instead of a MutableState parameter🟠

Parameters

Rule idChecksSeverity
ParameterOrderingOrder is required → modifier → optional → trailing content
TrailingLambdaThe content slot is the trailing lambda; event handlers are not
MutableParameterAvoid inherently mutable types (MutableList, ArrayList, …) as parameters🟠
ExplicitDependenciesMake injected ViewModels explicit parameters
ViewModelForwardingDon't forward a ViewModel into another composable🟠

Composables & Effects

Rule idChecksSeverity
ContentEmissionA composable emits content or returns a value, not both🟠
MultipleContentEmittersA composable emits a single piece of content🟠
ContentSlotReusedA content slot isn't invoked more than once on the same pass🟠
EffectKeysChanging captured values are passed as effect keys🟠
LambdaParameterInEffectLambda parameters used in effects are wrapped in rememberUpdatedState🟠
MovableContentmovableContentOf is remembered🔴
PreviewVisibility@Preview composables are private🟠
ComponentDefaultsVisibilityA <Component>Defaults object matches its composable's visibility🟠
LazyListMissingKeyLazy list items provide a stable key🔵
ComposableNestingDepthComposables are not nested deeper than the configured limit (opt-in)
LazyListContentTypeHeterogeneous lazy lists set a contentType🔵

Stricter

Rule idChecksSeverity
UnstableCollectionsPrefer ImmutableList / PersistentList over List, Set, Map🟠
CompositionLocalAllowlistCustom CompositionLocals are declared only when allowlisted (opt-in)🟠
Material2UsageMigrate androidx.compose.material (M2) imports to Material 3🔵

Suppress any rule with @Suppress("<RuleId>"), or toggle it in Settings → Tools → ComposeGuard.

Statistics Dashboard

ComposeGuard includes a tool window that tracks rule violations across your project.

ComposeGuard Statistics Dashboard

  • On-demand project scan — press Scan Project to count violations across every Kotlin file.
  • Category breakdown — see violations grouped by rule category.
  • Rule-level details — drill into specific rules.
  • Project overview — track overall code-quality trends.
  • Export — save the last scan as JSON or SARIF for CI dashboards and code-scanning tools.

Open it from ViewTool WindowsComposeGuard, or click a ComposeGuard gutter icon.

Configuration

Configure ComposeGuard at SettingsToolsComposeGuard.

ComposeGuard Settings - Disable Rules

  • Enable All Rules — master switch that selects or clears every rule at once.
  • Display options — toggle gutter icons and inlay hints.
  • Rule configuration — enable/disable individual rules or entire categories.
  • Analyze test sources — uncheck to leave test source roots alone.

Project configuration (.editorconfig)

ComposeGuard reads the same compose_* keys that the upstream Compose Rules ktlint ruleset uses, so a team can commit one .editorconfig and share it between the IDE plugin and CI. Keys are read from the nearest .editorconfig (walking up to the one marked root = true) in any section that applies to Kotlin files, for example [*.{kt,kts}]. Lists are comma-separated; entries containing regex metacharacters are treated as regular expressions.

KeyAffectsMeaning
compose_allowed_composable_function_namesComposableNamingNames (or regexes) exempt from the PascalCase/camelCase check
compose_content_emittersContentEmission, MultipleContentEmitters, ModifierRequired, TrailingLambdaExtra composables that count as emitting UI
compose_content_emitters_denylistsame as aboveComposables that must never count as emitting UI
compose_check_modifiers_for_visibilityModifierRequiredonly_public (default), public_and_internal, or all
compose_modifier_missing_ignore_annotatedModifierRequiredAnnotation names whose composables are skipped
compose_custom_modifiersModifierRequired, ModifierNaming, ModifierDefaultValue, ParameterOrdering, TrailingLambdaExtra types treated as Modifier (e.g. GlanceModifier)
compose_treat_as_lambdaParameterOrdering, TrailingLambdaType aliases treated as plain lambdas
compose_treat_as_composable_lambdaParameterOrdering, TrailingLambdaType aliases treated as @Composable content slots
compose_view_model_factoriesExplicitDependenciesExtra ViewModel factory functions
compose_allowed_composition_localsExplicitDependencies, CompositionLocalAllowlistCompositionLocals that may be read or declared
compose_allowed_state_holder_namesViewModelForwardingType name regexes that are not treated as forwarded ViewModels
compose_allowed_forwardingViewModelForwardingComposables a ViewModel may be forwarded to
compose_allowed_forwarding_of_typesViewModelForwardingViewModel types that may be forwarded
compose_allowed_from_m2Material2UsageMaterial 2 imports (or package prefixes) that are allowed
compose_allowed_lambda_parameter_namesEventParameterNamingEvent parameter names exempt from the present-tense check
compose_preview_naming_strategyPreviewNaminganywhere (default), suffix, or prefix
compose_composable_nesting_depth_thresholdComposableNestingDepthMaximum nesting depth (default 3)
compose_disallow_material2, compose_disallow_unstable_collections, compose_preview_naming_enabled, compose_composable_nesting_depth_enabledrule enablementtrue turns the rule on for this project regardless of IDE settings
root = true
[*.{kt,kts}]compose_allowed_from_m2 = androidx.compose.material.icons
compose_treat_as_composable_lambda = Slot
compose_composable_nesting_depth_enabled = true
compose_composable_nesting_depth_threshold = 4

Adopting ComposeGuard in an existing codebase

Adding the plugin to a large legacy project? Roll it out gradually instead of facing every warning at once:

  1. Start with the Stricter category off (Material2Usage, UnstableCollections).
  2. Enable categories one at a time as you refactor — the category checkbox toggles the whole group.
  3. Use @Suppress("<RuleId>") for individual, intentional exceptions.

Requirements & Compatibility

  • IntelliJ IDEA 2024.2+ or Android Studio Ladybug (2024.2)+
  • The bundled Kotlin plugin (enabled by default)
ComposeGuardSupported IDE builds
1.2.x2024.2 – 2026.2

Contributing

Contributions are welcome — issues and pull requests both.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/amazing-feature.
  3. Make your change and add tests (./gradlew :compose-guard:test).
  4. Commit and push, then open a Pull Request.

Credits

Built on the excellent Compose Rules guidelines by Nacho Lopez (mrmans0n).

Find this repository useful? ❤️

Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🤩

License

Designed and developed by 2025 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Made with ❤️ by androidpoet

About

Real-time detection of Jetpack Compose best practices and rule violations directly in Android Studio.

Topics

Resources

Stars

115 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

ComposeGuard

JetBrains PluginDownloadsLicenseProfileAndroid Weekly

Catch Jetpack Compose mistakes as you type — 39 best-practice rules from the Compose Rules guidelines, surfaced live in Android Studio & IntelliJ IDEA with inline highlights, gutter icons, and one-click fixes.


Preview

ComposeGuard Preview

Why ComposeGuard?

The Compose Rules catch the subtle mistakes that hurt Compose code — missing Modifier parameters, un-remembered state, unstable collections, reused modifiers, and dozens more. ComposeGuard brings those checks into the editor, so you fix them while the code is still fresh instead of discovering them in a build log or a code review.

  • Instant — analysis runs as you type; no build, no Gradle task, no CI round-trip.
  • 🎯 Accurate — rules are PSI-based and tuned to avoid false positives on valid patterns (overrides, scoped slots, mutually-exclusive branches, run-once effects, …).
  • 🛠 Actionable — most violations come with a quick fix (Alt+Enter) and a detailed explanation of why it matters.
  • 🎚 Configurable — enable/disable any rule or whole category, or suppress per declaration.

Table of Contents

Features

  • Real-time highlighting — violations appear as colored underlines while you edit.
  • Gutter icons — a color-coded dot per @Composable summarizes its status at a glance:
    • 🔴 Error 🟠 Warning ⚪ Weak warning 🔵 Info
  • Inline hints — compact badges next to function names show rule violations.
  • Hover tooltips — every violation explains the problem, the reasoning, and the fix.
  • Quick fixes — rename, add a modifier parameter, wrap in remember, switch to a type-specific state, make a preview private, swap to an immutable collection, and more.
  • 39 rules across 6 categories — see the full Rule Reference.

Installation

Install from Marketplace

  1. Open Android Studio or IntelliJ IDEA.
  2. Go to SettingsPluginsMarketplace.
  3. Search for ComposeGuard.
  4. Click Install and restart when prompted.

Or install directly from the JetBrains Marketplace.

Quick Start

Once installed, ComposeGuard automatically analyzes any Kotlin file containing @Composable functions — no configuration required. Here are a few things it catches:

// 🟠 Naming: Unit-returning composables should be PascalCase
@Composable
funuserCard(user:User) { } // → rename to "UserCard"// 🟠 Modifier: public UI composables should expose a Modifier
@Composable
funProductCard(product:Product) { // → add `modifier: Modifier = Modifier`Column { Text(product.name) }
}
// 🔴 State: state must be remembered
@Composable
funCounter() {
val count = mutableStateOf(0) // → wrap in remember { }
}
// 🟠 Stricter: prefer stable collections
@Composable
funItemList(items:List<Item>) { } // → use ImmutableList<Item>

Hover any highlight for the full explanation, or press Alt+Enter to apply a fix.

Suppressing Rules

To intentionally allow a violation, annotate the declaration with @Suppress using the rule id (the same id shown in the warning, e.g. ModifierRequired). The quick fix can insert this for you:

@Suppress("ModifierRequired")
@Composable
funSplashLogo() {
Image(painterResource(R.drawable.logo), contentDescription =null)
}
// Multiple rules at once:
@Suppress("ModifierRequired", "ComposableNaming")
@Composable
funsplash() { /* ... */ }

Suppression works at the function, property, or class level. To turn rules off project-wide instead, use Configuration.

Rule Reference

ComposeGuard ships 39 rules based on the Compose Rules guidelines. Severity legend: 🔴 Error · 🟠 Warning · ⚪ Weak warning · 🔵 Info.

Naming

Rule idChecksSeverity
ComposableNamingUnit-returning composables use PascalCase; value-returning use camelCase🟠
CompositionLocalNamingCompositionLocal properties are prefixed with Local🟠
PreviewNaming@Preview functions reference Preview in their name
MultipreviewNamingMultipreview annotation classes reference Preview
ComposableAnnotationNaming@ComposableTargetMarker annotations end with Composable
EventParameterNamingEvent lambdas use present tense (onClick, not onClicked)

Modifiers

Rule idChecksSeverity
ModifierRequiredPublic, UI-emitting composables expose a Modifier parameter🟠
ModifierDefaultValuemodifier parameters default to Modifier🟠
ModifierNamingThe main modifier is named modifier; others follow xModifier
ModifierTopMostThe modifier is applied to the root-most layout🟠
ModifierReuseThe same modifier isn't applied to multiple live nodes🟠
ModifierOrderModifier chain order is intentional (e.g. padding before clickable)🟠
AvoidComposedPrefer Modifier.Node over the deprecated composed { } factory🟠

State

Rule idChecksSeverity
RememberStatemutableStateOf and friends are wrapped in remember { }🔴
TypeSpecificStatePrimitives use mutableIntStateOf / mutableFloatStateOf / …🟠
DerivedStateOfCandidateValues computed from state use derivedStateOf🟠
FrequentRecompositionHot observable sources use lifecycle-aware collection🟠
DeferStateReadsFast-changing state reads are deferred to lambda modifiers🟠
HoistStateState is hoisted to the appropriate level🔵
MutableStateParameterPass value + callback instead of a MutableState parameter🟠

Parameters

Rule idChecksSeverity
ParameterOrderingOrder is required → modifier → optional → trailing content
TrailingLambdaThe content slot is the trailing lambda; event handlers are not
MutableParameterAvoid inherently mutable types (MutableList, ArrayList, …) as parameters🟠
ExplicitDependenciesMake injected ViewModels explicit parameters
ViewModelForwardingDon't forward a ViewModel into another composable🟠

Composables & Effects

Rule idChecksSeverity
ContentEmissionA composable emits content or returns a value, not both🟠
MultipleContentEmittersA composable emits a single piece of content🟠
ContentSlotReusedA content slot isn't invoked more than once on the same pass🟠
EffectKeysChanging captured values are passed as effect keys🟠
LambdaParameterInEffectLambda parameters used in effects are wrapped in rememberUpdatedState🟠
MovableContentmovableContentOf is remembered🔴
PreviewVisibility@Preview composables are private🟠
ComponentDefaultsVisibilityA <Component>Defaults object matches its composable's visibility🟠
LazyListMissingKeyLazy list items provide a stable key🔵
ComposableNestingDepthComposables are not nested deeper than the configured limit (opt-in)
LazyListContentTypeHeterogeneous lazy lists set a contentType🔵

Stricter

Rule idChecksSeverity
UnstableCollectionsPrefer ImmutableList / PersistentList over List, Set, Map🟠
CompositionLocalAllowlistCustom CompositionLocals are declared only when allowlisted (opt-in)🟠
Material2UsageMigrate androidx.compose.material (M2) imports to Material 3🔵

Suppress any rule with @Suppress("<RuleId>"), or toggle it in Settings → Tools → ComposeGuard.

Statistics Dashboard

ComposeGuard includes a tool window that tracks rule violations across your project.

ComposeGuard Statistics Dashboard

  • On-demand project scan — press Scan Project to count violations across every Kotlin file.
  • Category breakdown — see violations grouped by rule category.
  • Rule-level details — drill into specific rules.
  • Project overview — track overall code-quality trends.
  • Export — save the last scan as JSON or SARIF for CI dashboards and code-scanning tools.

Open it from ViewTool WindowsComposeGuard, or click a ComposeGuard gutter icon.

Configuration

Configure ComposeGuard at SettingsToolsComposeGuard.

ComposeGuard Settings - Disable Rules

  • Enable All Rules — master switch that selects or clears every rule at once.
  • Display options — toggle gutter icons and inlay hints.
  • Rule configuration — enable/disable individual rules or entire categories.
  • Analyze test sources — uncheck to leave test source roots alone.

Project configuration (.editorconfig)

ComposeGuard reads the same compose_* keys that the upstream Compose Rules ktlint ruleset uses, so a team can commit one .editorconfig and share it between the IDE plugin and CI. Keys are read from the nearest .editorconfig (walking up to the one marked root = true) in any section that applies to Kotlin files, for example [*.{kt,kts}]. Lists are comma-separated; entries containing regex metacharacters are treated as regular expressions.

KeyAffectsMeaning
compose_allowed_composable_function_namesComposableNamingNames (or regexes) exempt from the PascalCase/camelCase check
compose_content_emittersContentEmission, MultipleContentEmitters, ModifierRequired, TrailingLambdaExtra composables that count as emitting UI
compose_content_emitters_denylistsame as aboveComposables that must never count as emitting UI
compose_check_modifiers_for_visibilityModifierRequiredonly_public (default), public_and_internal, or all
compose_modifier_missing_ignore_annotatedModifierRequiredAnnotation names whose composables are skipped
compose_custom_modifiersModifierRequired, ModifierNaming, ModifierDefaultValue, ParameterOrdering, TrailingLambdaExtra types treated as Modifier (e.g. GlanceModifier)
compose_treat_as_lambdaParameterOrdering, TrailingLambdaType aliases treated as plain lambdas
compose_treat_as_composable_lambdaParameterOrdering, TrailingLambdaType aliases treated as @Composable content slots
compose_view_model_factoriesExplicitDependenciesExtra ViewModel factory functions
compose_allowed_composition_localsExplicitDependencies, CompositionLocalAllowlistCompositionLocals that may be read or declared
compose_allowed_state_holder_namesViewModelForwardingType name regexes that are not treated as forwarded ViewModels
compose_allowed_forwardingViewModelForwardingComposables a ViewModel may be forwarded to
compose_allowed_forwarding_of_typesViewModelForwardingViewModel types that may be forwarded
compose_allowed_from_m2Material2UsageMaterial 2 imports (or package prefixes) that are allowed
compose_allowed_lambda_parameter_namesEventParameterNamingEvent parameter names exempt from the present-tense check
compose_preview_naming_strategyPreviewNaminganywhere (default), suffix, or prefix
compose_composable_nesting_depth_thresholdComposableNestingDepthMaximum nesting depth (default 3)
compose_disallow_material2, compose_disallow_unstable_collections, compose_preview_naming_enabled, compose_composable_nesting_depth_enabledrule enablementtrue turns the rule on for this project regardless of IDE settings
root = true
[*.{kt,kts}]compose_allowed_from_m2 = androidx.compose.material.icons
compose_treat_as_composable_lambda = Slot
compose_composable_nesting_depth_enabled = true
compose_composable_nesting_depth_threshold = 4

Adopting ComposeGuard in an existing codebase

Adding the plugin to a large legacy project? Roll it out gradually instead of facing every warning at once:

  1. Start with the Stricter category off (Material2Usage, UnstableCollections).
  2. Enable categories one at a time as you refactor — the category checkbox toggles the whole group.
  3. Use @Suppress("<RuleId>") for individual, intentional exceptions.

Requirements & Compatibility

  • IntelliJ IDEA 2024.2+ or Android Studio Ladybug (2024.2)+
  • The bundled Kotlin plugin (enabled by default)
ComposeGuardSupported IDE builds
1.2.x2024.2 – 2026.2

Contributing

Contributions are welcome — issues and pull requests both.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/amazing-feature.
  3. Make your change and add tests (./gradlew :compose-guard:test).
  4. Commit and push, then open a Pull Request.

Credits

Built on the excellent Compose Rules guidelines by Nacho Lopez (mrmans0n).

Find this repository useful? ❤️

Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🤩

License

Designed and developed by 2025 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Made with ❤️ by androidpoet

About

Real-time detection of Jetpack Compose best practices and rule violations directly in Android Studio.

Topics

Resources

Stars

115 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

ComposeGuard

JetBrains PluginDownloadsLicenseProfileAndroid Weekly

Catch Jetpack Compose mistakes as you type — 39 best-practice rules from the Compose Rules guidelines, surfaced live in Android Studio & IntelliJ IDEA with inline highlights, gutter icons, and one-click fixes.


Preview

ComposeGuard Preview

Why ComposeGuard?

The Compose Rules catch the subtle mistakes that hurt Compose code — missing Modifier parameters, un-remembered state, unstable collections, reused modifiers, and dozens more. ComposeGuard brings those checks into the editor, so you fix them while the code is still fresh instead of discovering them in a build log or a code review.

  • Instant — analysis runs as you type; no build, no Gradle task, no CI round-trip.
  • 🎯 Accurate — rules are PSI-based and tuned to avoid false positives on valid patterns (overrides, scoped slots, mutually-exclusive branches, run-once effects, …).
  • 🛠 Actionable — most violations come with a quick fix (Alt+Enter) and a detailed explanation of why it matters.
  • 🎚 Configurable — enable/disable any rule or whole category, or suppress per declaration.

Table of Contents

Features

  • Real-time highlighting — violations appear as colored underlines while you edit.
  • Gutter icons — a color-coded dot per @Composable summarizes its status at a glance:
    • 🔴 Error 🟠 Warning ⚪ Weak warning 🔵 Info
  • Inline hints — compact badges next to function names show rule violations.
  • Hover tooltips — every violation explains the problem, the reasoning, and the fix.
  • Quick fixes — rename, add a modifier parameter, wrap in remember, switch to a type-specific state, make a preview private, swap to an immutable collection, and more.
  • 39 rules across 6 categories — see the full Rule Reference.

Installation

Install from Marketplace

  1. Open Android Studio or IntelliJ IDEA.
  2. Go to SettingsPluginsMarketplace.
  3. Search for ComposeGuard.
  4. Click Install and restart when prompted.

Or install directly from the JetBrains Marketplace.

Quick Start

Once installed, ComposeGuard automatically analyzes any Kotlin file containing @Composable functions — no configuration required. Here are a few things it catches:

// 🟠 Naming: Unit-returning composables should be PascalCase
@Composable
funuserCard(user:User) { } // → rename to "UserCard"// 🟠 Modifier: public UI composables should expose a Modifier
@Composable
funProductCard(product:Product) { // → add `modifier: Modifier = Modifier`Column { Text(product.name) }
}
// 🔴 State: state must be remembered
@Composable
funCounter() {
val count = mutableStateOf(0) // → wrap in remember { }
}
// 🟠 Stricter: prefer stable collections
@Composable
funItemList(items:List<Item>) { } // → use ImmutableList<Item>

Hover any highlight for the full explanation, or press Alt+Enter to apply a fix.

Suppressing Rules

To intentionally allow a violation, annotate the declaration with @Suppress using the rule id (the same id shown in the warning, e.g. ModifierRequired). The quick fix can insert this for you:

@Suppress("ModifierRequired")
@Composable
funSplashLogo() {
Image(painterResource(R.drawable.logo), contentDescription =null)
}
// Multiple rules at once:
@Suppress("ModifierRequired", "ComposableNaming")
@Composable
funsplash() { /* ... */ }

Suppression works at the function, property, or class level. To turn rules off project-wide instead, use Configuration.

Rule Reference

ComposeGuard ships 39 rules based on the Compose Rules guidelines. Severity legend: 🔴 Error · 🟠 Warning · ⚪ Weak warning · 🔵 Info.

Naming

Rule idChecksSeverity
ComposableNamingUnit-returning composables use PascalCase; value-returning use camelCase🟠
CompositionLocalNamingCompositionLocal properties are prefixed with Local🟠
PreviewNaming@Preview functions reference Preview in their name
MultipreviewNamingMultipreview annotation classes reference Preview
ComposableAnnotationNaming@ComposableTargetMarker annotations end with Composable
EventParameterNamingEvent lambdas use present tense (onClick, not onClicked)

Modifiers

Rule idChecksSeverity
ModifierRequiredPublic, UI-emitting composables expose a Modifier parameter🟠
ModifierDefaultValuemodifier parameters default to Modifier🟠
ModifierNamingThe main modifier is named modifier; others follow xModifier
ModifierTopMostThe modifier is applied to the root-most layout🟠
ModifierReuseThe same modifier isn't applied to multiple live nodes🟠
ModifierOrderModifier chain order is intentional (e.g. padding before clickable)🟠
AvoidComposedPrefer Modifier.Node over the deprecated composed { } factory🟠

State

Rule idChecksSeverity
RememberStatemutableStateOf and friends are wrapped in remember { }🔴
TypeSpecificStatePrimitives use mutableIntStateOf / mutableFloatStateOf / …🟠
DerivedStateOfCandidateValues computed from state use derivedStateOf🟠
FrequentRecompositionHot observable sources use lifecycle-aware collection🟠
DeferStateReadsFast-changing state reads are deferred to lambda modifiers🟠
HoistStateState is hoisted to the appropriate level🔵
MutableStateParameterPass value + callback instead of a MutableState parameter🟠

Parameters

Rule idChecksSeverity
ParameterOrderingOrder is required → modifier → optional → trailing content
TrailingLambdaThe content slot is the trailing lambda; event handlers are not
MutableParameterAvoid inherently mutable types (MutableList, ArrayList, …) as parameters🟠
ExplicitDependenciesMake injected ViewModels explicit parameters
ViewModelForwardingDon't forward a ViewModel into another composable🟠

Composables & Effects

Rule idChecksSeverity
ContentEmissionA composable emits content or returns a value, not both🟠
MultipleContentEmittersA composable emits a single piece of content🟠
ContentSlotReusedA content slot isn't invoked more than once on the same pass🟠
EffectKeysChanging captured values are passed as effect keys🟠
LambdaParameterInEffectLambda parameters used in effects are wrapped in rememberUpdatedState🟠
MovableContentmovableContentOf is remembered🔴
PreviewVisibility@Preview composables are private🟠
ComponentDefaultsVisibilityA <Component>Defaults object matches its composable's visibility🟠
LazyListMissingKeyLazy list items provide a stable key🔵
ComposableNestingDepthComposables are not nested deeper than the configured limit (opt-in)
LazyListContentTypeHeterogeneous lazy lists set a contentType🔵

Stricter

Rule idChecksSeverity
UnstableCollectionsPrefer ImmutableList / PersistentList over List, Set, Map🟠
CompositionLocalAllowlistCustom CompositionLocals are declared only when allowlisted (opt-in)🟠
Material2UsageMigrate androidx.compose.material (M2) imports to Material 3🔵

Suppress any rule with @Suppress("<RuleId>"), or toggle it in Settings → Tools → ComposeGuard.

Statistics Dashboard

ComposeGuard includes a tool window that tracks rule violations across your project.

ComposeGuard Statistics Dashboard

  • On-demand project scan — press Scan Project to count violations across every Kotlin file.
  • Category breakdown — see violations grouped by rule category.
  • Rule-level details — drill into specific rules.
  • Project overview — track overall code-quality trends.
  • Export — save the last scan as JSON or SARIF for CI dashboards and code-scanning tools.

Open it from ViewTool WindowsComposeGuard, or click a ComposeGuard gutter icon.

Configuration

Configure ComposeGuard at SettingsToolsComposeGuard.

ComposeGuard Settings - Disable Rules

  • Enable All Rules — master switch that selects or clears every rule at once.
  • Display options — toggle gutter icons and inlay hints.
  • Rule configuration — enable/disable individual rules or entire categories.
  • Analyze test sources — uncheck to leave test source roots alone.

Project configuration (.editorconfig)

ComposeGuard reads the same compose_* keys that the upstream Compose Rules ktlint ruleset uses, so a team can commit one .editorconfig and share it between the IDE plugin and CI. Keys are read from the nearest .editorconfig (walking up to the one marked root = true) in any section that applies to Kotlin files, for example [*.{kt,kts}]. Lists are comma-separated; entries containing regex metacharacters are treated as regular expressions.

KeyAffectsMeaning
compose_allowed_composable_function_namesComposableNamingNames (or regexes) exempt from the PascalCase/camelCase check
compose_content_emittersContentEmission, MultipleContentEmitters, ModifierRequired, TrailingLambdaExtra composables that count as emitting UI
compose_content_emitters_denylistsame as aboveComposables that must never count as emitting UI
compose_check_modifiers_for_visibilityModifierRequiredonly_public (default), public_and_internal, or all
compose_modifier_missing_ignore_annotatedModifierRequiredAnnotation names whose composables are skipped
compose_custom_modifiersModifierRequired, ModifierNaming, ModifierDefaultValue, ParameterOrdering, TrailingLambdaExtra types treated as Modifier (e.g. GlanceModifier)
compose_treat_as_lambdaParameterOrdering, TrailingLambdaType aliases treated as plain lambdas
compose_treat_as_composable_lambdaParameterOrdering, TrailingLambdaType aliases treated as @Composable content slots
compose_view_model_factoriesExplicitDependenciesExtra ViewModel factory functions
compose_allowed_composition_localsExplicitDependencies, CompositionLocalAllowlistCompositionLocals that may be read or declared
compose_allowed_state_holder_namesViewModelForwardingType name regexes that are not treated as forwarded ViewModels
compose_allowed_forwardingViewModelForwardingComposables a ViewModel may be forwarded to
compose_allowed_forwarding_of_typesViewModelForwardingViewModel types that may be forwarded
compose_allowed_from_m2Material2UsageMaterial 2 imports (or package prefixes) that are allowed
compose_allowed_lambda_parameter_namesEventParameterNamingEvent parameter names exempt from the present-tense check
compose_preview_naming_strategyPreviewNaminganywhere (default), suffix, or prefix
compose_composable_nesting_depth_thresholdComposableNestingDepthMaximum nesting depth (default 3)
compose_disallow_material2, compose_disallow_unstable_collections, compose_preview_naming_enabled, compose_composable_nesting_depth_enabledrule enablementtrue turns the rule on for this project regardless of IDE settings
root = true
[*.{kt,kts}]compose_allowed_from_m2 = androidx.compose.material.icons
compose_treat_as_composable_lambda = Slot
compose_composable_nesting_depth_enabled = true
compose_composable_nesting_depth_threshold = 4

Adopting ComposeGuard in an existing codebase

Adding the plugin to a large legacy project? Roll it out gradually instead of facing every warning at once:

  1. Start with the Stricter category off (Material2Usage, UnstableCollections).
  2. Enable categories one at a time as you refactor — the category checkbox toggles the whole group.
  3. Use @Suppress("<RuleId>") for individual, intentional exceptions.

Requirements & Compatibility

  • IntelliJ IDEA 2024.2+ or Android Studio Ladybug (2024.2)+
  • The bundled Kotlin plugin (enabled by default)
ComposeGuardSupported IDE builds
1.2.x2024.2 – 2026.2

Contributing

Contributions are welcome — issues and pull requests both.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/amazing-feature.
  3. Make your change and add tests (./gradlew :compose-guard:test).
  4. Commit and push, then open a Pull Request.

Credits

Built on the excellent Compose Rules guidelines by Nacho Lopez (mrmans0n).

Find this repository useful? ❤️

Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🤩

License

Designed and developed by 2025 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Made with ❤️ by androidpoet

About

Real-time detection of Jetpack Compose best practices and rule violations directly in Android Studio.

Topics

Resources

Stars

115 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

ComposeGuard

JetBrains PluginDownloadsLicenseProfileAndroid Weekly

Catch Jetpack Compose mistakes as you type — 39 best-practice rules from the Compose Rules guidelines, surfaced live in Android Studio & IntelliJ IDEA with inline highlights, gutter icons, and one-click fixes.


Preview

ComposeGuard Preview

Why ComposeGuard?

The Compose Rules catch the subtle mistakes that hurt Compose code — missing Modifier parameters, un-remembered state, unstable collections, reused modifiers, and dozens more. ComposeGuard brings those checks into the editor, so you fix them while the code is still fresh instead of discovering them in a build log or a code review.

  • Instant — analysis runs as you type; no build, no Gradle task, no CI round-trip.
  • 🎯 Accurate — rules are PSI-based and tuned to avoid false positives on valid patterns (overrides, scoped slots, mutually-exclusive branches, run-once effects, …).
  • 🛠 Actionable — most violations come with a quick fix (Alt+Enter) and a detailed explanation of why it matters.
  • 🎚 Configurable — enable/disable any rule or whole category, or suppress per declaration.

Table of Contents

Features

  • Real-time highlighting — violations appear as colored underlines while you edit.
  • Gutter icons — a color-coded dot per @Composable summarizes its status at a glance:
    • 🔴 Error 🟠 Warning ⚪ Weak warning 🔵 Info
  • Inline hints — compact badges next to function names show rule violations.
  • Hover tooltips — every violation explains the problem, the reasoning, and the fix.
  • Quick fixes — rename, add a modifier parameter, wrap in remember, switch to a type-specific state, make a preview private, swap to an immutable collection, and more.
  • 39 rules across 6 categories — see the full Rule Reference.

Installation

Install from Marketplace

  1. Open Android Studio or IntelliJ IDEA.
  2. Go to SettingsPluginsMarketplace.
  3. Search for ComposeGuard.
  4. Click Install and restart when prompted.

Or install directly from the JetBrains Marketplace.

Quick Start

Once installed, ComposeGuard automatically analyzes any Kotlin file containing @Composable functions — no configuration required. Here are a few things it catches:

// 🟠 Naming: Unit-returning composables should be PascalCase
@Composable
funuserCard(user:User) { } // → rename to "UserCard"// 🟠 Modifier: public UI composables should expose a Modifier
@Composable
funProductCard(product:Product) { // → add `modifier: Modifier = Modifier`Column { Text(product.name) }
}
// 🔴 State: state must be remembered
@Composable
funCounter() {
val count = mutableStateOf(0) // → wrap in remember { }
}
// 🟠 Stricter: prefer stable collections
@Composable
funItemList(items:List<Item>) { } // → use ImmutableList<Item>

Hover any highlight for the full explanation, or press Alt+Enter to apply a fix.

Suppressing Rules

To intentionally allow a violation, annotate the declaration with @Suppress using the rule id (the same id shown in the warning, e.g. ModifierRequired). The quick fix can insert this for you:

@Suppress("ModifierRequired")
@Composable
funSplashLogo() {
Image(painterResource(R.drawable.logo), contentDescription =null)
}
// Multiple rules at once:
@Suppress("ModifierRequired", "ComposableNaming")
@Composable
funsplash() { /* ... */ }

Suppression works at the function, property, or class level. To turn rules off project-wide instead, use Configuration.

Rule Reference

ComposeGuard ships 39 rules based on the Compose Rules guidelines. Severity legend: 🔴 Error · 🟠 Warning · ⚪ Weak warning · 🔵 Info.

Naming

Rule idChecksSeverity
ComposableNamingUnit-returning composables use PascalCase; value-returning use camelCase🟠
CompositionLocalNamingCompositionLocal properties are prefixed with Local🟠
PreviewNaming@Preview functions reference Preview in their name
MultipreviewNamingMultipreview annotation classes reference Preview
ComposableAnnotationNaming@ComposableTargetMarker annotations end with Composable
EventParameterNamingEvent lambdas use present tense (onClick, not onClicked)

Modifiers

Rule idChecksSeverity
ModifierRequiredPublic, UI-emitting composables expose a Modifier parameter🟠
ModifierDefaultValuemodifier parameters default to Modifier🟠
ModifierNamingThe main modifier is named modifier; others follow xModifier
ModifierTopMostThe modifier is applied to the root-most layout🟠
ModifierReuseThe same modifier isn't applied to multiple live nodes🟠
ModifierOrderModifier chain order is intentional (e.g. padding before clickable)🟠
AvoidComposedPrefer Modifier.Node over the deprecated composed { } factory🟠

State

Rule idChecksSeverity
RememberStatemutableStateOf and friends are wrapped in remember { }🔴
TypeSpecificStatePrimitives use mutableIntStateOf / mutableFloatStateOf / …🟠
DerivedStateOfCandidateValues computed from state use derivedStateOf🟠
FrequentRecompositionHot observable sources use lifecycle-aware collection🟠
DeferStateReadsFast-changing state reads are deferred to lambda modifiers🟠
HoistStateState is hoisted to the appropriate level🔵
MutableStateParameterPass value + callback instead of a MutableState parameter🟠

Parameters

Rule idChecksSeverity
ParameterOrderingOrder is required → modifier → optional → trailing content
TrailingLambdaThe content slot is the trailing lambda; event handlers are not
MutableParameterAvoid inherently mutable types (MutableList, ArrayList, …) as parameters🟠
ExplicitDependenciesMake injected ViewModels explicit parameters
ViewModelForwardingDon't forward a ViewModel into another composable🟠

Composables & Effects

Rule idChecksSeverity
ContentEmissionA composable emits content or returns a value, not both🟠
MultipleContentEmittersA composable emits a single piece of content🟠
ContentSlotReusedA content slot isn't invoked more than once on the same pass🟠
EffectKeysChanging captured values are passed as effect keys🟠
LambdaParameterInEffectLambda parameters used in effects are wrapped in rememberUpdatedState🟠
MovableContentmovableContentOf is remembered🔴
PreviewVisibility@Preview composables are private🟠
ComponentDefaultsVisibilityA <Component>Defaults object matches its composable's visibility🟠
LazyListMissingKeyLazy list items provide a stable key🔵
ComposableNestingDepthComposables are not nested deeper than the configured limit (opt-in)
LazyListContentTypeHeterogeneous lazy lists set a contentType🔵

Stricter

Rule idChecksSeverity
UnstableCollectionsPrefer ImmutableList / PersistentList over List, Set, Map🟠
CompositionLocalAllowlistCustom CompositionLocals are declared only when allowlisted (opt-in)🟠
Material2UsageMigrate androidx.compose.material (M2) imports to Material 3🔵

Suppress any rule with @Suppress("<RuleId>"), or toggle it in Settings → Tools → ComposeGuard.

Statistics Dashboard

ComposeGuard includes a tool window that tracks rule violations across your project.

ComposeGuard Statistics Dashboard

  • On-demand project scan — press Scan Project to count violations across every Kotlin file.
  • Category breakdown — see violations grouped by rule category.
  • Rule-level details — drill into specific rules.
  • Project overview — track overall code-quality trends.
  • Export — save the last scan as JSON or SARIF for CI dashboards and code-scanning tools.

Open it from ViewTool WindowsComposeGuard, or click a ComposeGuard gutter icon.

Configuration

Configure ComposeGuard at SettingsToolsComposeGuard.

ComposeGuard Settings - Disable Rules

  • Enable All Rules — master switch that selects or clears every rule at once.
  • Display options — toggle gutter icons and inlay hints.
  • Rule configuration — enable/disable individual rules or entire categories.
  • Analyze test sources — uncheck to leave test source roots alone.

Project configuration (.editorconfig)

ComposeGuard reads the same compose_* keys that the upstream Compose Rules ktlint ruleset uses, so a team can commit one .editorconfig and share it between the IDE plugin and CI. Keys are read from the nearest .editorconfig (walking up to the one marked root = true) in any section that applies to Kotlin files, for example [*.{kt,kts}]. Lists are comma-separated; entries containing regex metacharacters are treated as regular expressions.

KeyAffectsMeaning
compose_allowed_composable_function_namesComposableNamingNames (or regexes) exempt from the PascalCase/camelCase check
compose_content_emittersContentEmission, MultipleContentEmitters, ModifierRequired, TrailingLambdaExtra composables that count as emitting UI
compose_content_emitters_denylistsame as aboveComposables that must never count as emitting UI
compose_check_modifiers_for_visibilityModifierRequiredonly_public (default), public_and_internal, or all
compose_modifier_missing_ignore_annotatedModifierRequiredAnnotation names whose composables are skipped
compose_custom_modifiersModifierRequired, ModifierNaming, ModifierDefaultValue, ParameterOrdering, TrailingLambdaExtra types treated as Modifier (e.g. GlanceModifier)
compose_treat_as_lambdaParameterOrdering, TrailingLambdaType aliases treated as plain lambdas
compose_treat_as_composable_lambdaParameterOrdering, TrailingLambdaType aliases treated as @Composable content slots
compose_view_model_factoriesExplicitDependenciesExtra ViewModel factory functions
compose_allowed_composition_localsExplicitDependencies, CompositionLocalAllowlistCompositionLocals that may be read or declared
compose_allowed_state_holder_namesViewModelForwardingType name regexes that are not treated as forwarded ViewModels
compose_allowed_forwardingViewModelForwardingComposables a ViewModel may be forwarded to
compose_allowed_forwarding_of_typesViewModelForwardingViewModel types that may be forwarded
compose_allowed_from_m2Material2UsageMaterial 2 imports (or package prefixes) that are allowed
compose_allowed_lambda_parameter_namesEventParameterNamingEvent parameter names exempt from the present-tense check
compose_preview_naming_strategyPreviewNaminganywhere (default), suffix, or prefix
compose_composable_nesting_depth_thresholdComposableNestingDepthMaximum nesting depth (default 3)
compose_disallow_material2, compose_disallow_unstable_collections, compose_preview_naming_enabled, compose_composable_nesting_depth_enabledrule enablementtrue turns the rule on for this project regardless of IDE settings
root = true
[*.{kt,kts}]compose_allowed_from_m2 = androidx.compose.material.icons
compose_treat_as_composable_lambda = Slot
compose_composable_nesting_depth_enabled = true
compose_composable_nesting_depth_threshold = 4

Adopting ComposeGuard in an existing codebase

Adding the plugin to a large legacy project? Roll it out gradually instead of facing every warning at once:

  1. Start with the Stricter category off (Material2Usage, UnstableCollections).
  2. Enable categories one at a time as you refactor — the category checkbox toggles the whole group.
  3. Use @Suppress("<RuleId>") for individual, intentional exceptions.

Requirements & Compatibility

  • IntelliJ IDEA 2024.2+ or Android Studio Ladybug (2024.2)+
  • The bundled Kotlin plugin (enabled by default)
ComposeGuardSupported IDE builds
1.2.x2024.2 – 2026.2

Contributing

Contributions are welcome — issues and pull requests both.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/amazing-feature.
  3. Make your change and add tests (./gradlew :compose-guard:test).
  4. Commit and push, then open a Pull Request.

Credits

Built on the excellent Compose Rules guidelines by Nacho Lopez (mrmans0n).

Find this repository useful? ❤️

Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🤩

License

Designed and developed by 2025 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Made with ❤️ by androidpoet

About

Real-time detection of Jetpack Compose best practices and rule violations directly in Android Studio.

Topics

Resources

Stars

115 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages