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.
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.
- Features
- Installation
- Quick Start
- Suppressing Rules
- Rule Reference
- Statistics Dashboard
- Configuration
- Requirements & Compatibility
- Contributing
- Credits
- License
- Real-time highlighting — violations appear as colored underlines while you edit.
- Gutter icons — a color-coded dot per
@Composablesummarizes 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
modifierparameter, wrap inremember, 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.
- Open Android Studio or IntelliJ IDEA.
- Go to Settings → Plugins → Marketplace.
- Search for ComposeGuard.
- Click Install and restart when prompted.
Or install directly from the JetBrains Marketplace.
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.
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.
ComposeGuard ships 39 rules based on the Compose Rules guidelines. Severity legend: 🔴 Error · 🟠 Warning · ⚪ Weak warning · 🔵 Info.
| Rule id | Checks | Severity |
|---|---|---|
ComposableNaming | Unit-returning composables use PascalCase; value-returning use camelCase | 🟠 |
CompositionLocalNaming | CompositionLocal properties are prefixed with Local | 🟠 |
PreviewNaming | @Preview functions reference Preview in their name | ⚪ |
MultipreviewNaming | Multipreview annotation classes reference Preview | ⚪ |
ComposableAnnotationNaming | @ComposableTargetMarker annotations end with Composable | ⚪ |
EventParameterNaming | Event lambdas use present tense (onClick, not onClicked) | ⚪ |
| Rule id | Checks | Severity |
|---|---|---|
ModifierRequired | Public, UI-emitting composables expose a Modifier parameter | 🟠 |
ModifierDefaultValue | modifier parameters default to Modifier | 🟠 |
ModifierNaming | The main modifier is named modifier; others follow xModifier | ⚪ |
ModifierTopMost | The modifier is applied to the root-most layout | 🟠 |
ModifierReuse | The same modifier isn't applied to multiple live nodes | 🟠 |
ModifierOrder | Modifier chain order is intentional (e.g. padding before clickable) | 🟠 |
AvoidComposed | Prefer Modifier.Node over the deprecated composed { } factory | 🟠 |
| Rule id | Checks | Severity |
|---|---|---|
RememberState | mutableStateOf and friends are wrapped in remember { } | 🔴 |
TypeSpecificState | Primitives use mutableIntStateOf / mutableFloatStateOf / … | 🟠 |
DerivedStateOfCandidate | Values computed from state use derivedStateOf | 🟠 |
FrequentRecomposition | Hot observable sources use lifecycle-aware collection | 🟠 |
DeferStateReads | Fast-changing state reads are deferred to lambda modifiers | 🟠 |
HoistState | State is hoisted to the appropriate level | 🔵 |
MutableStateParameter | Pass value + callback instead of a MutableState parameter | 🟠 |
| Rule id | Checks | Severity |
|---|---|---|
ParameterOrdering | Order is required → modifier → optional → trailing content | ⚪ |
TrailingLambda | The content slot is the trailing lambda; event handlers are not | ⚪ |
MutableParameter | Avoid inherently mutable types (MutableList, ArrayList, …) as parameters | 🟠 |
ExplicitDependencies | Make injected ViewModels explicit parameters | ⚪ |
ViewModelForwarding | Don't forward a ViewModel into another composable | 🟠 |
| Rule id | Checks | Severity |
|---|---|---|
ContentEmission | A composable emits content or returns a value, not both | 🟠 |
MultipleContentEmitters | A composable emits a single piece of content | 🟠 |
ContentSlotReused | A content slot isn't invoked more than once on the same pass | 🟠 |
EffectKeys | Changing captured values are passed as effect keys | 🟠 |
LambdaParameterInEffect | Lambda parameters used in effects are wrapped in rememberUpdatedState | 🟠 |
MovableContent | movableContentOf is remembered | 🔴 |
PreviewVisibility | @Preview composables are private | 🟠 |
ComponentDefaultsVisibility | A <Component>Defaults object matches its composable's visibility | 🟠 |
LazyListMissingKey | Lazy list items provide a stable key | 🔵 |
ComposableNestingDepth | Composables are not nested deeper than the configured limit (opt-in) | ⚪ |
LazyListContentType | Heterogeneous lazy lists set a contentType | 🔵 |
| Rule id | Checks | Severity |
|---|---|---|
UnstableCollections | Prefer ImmutableList / PersistentList over List, Set, Map | 🟠 |
CompositionLocalAllowlist | Custom CompositionLocals are declared only when allowlisted (opt-in) | 🟠 |
Material2Usage | Migrate androidx.compose.material (M2) imports to Material 3 | 🔵 |
Suppress any rule with
@Suppress("<RuleId>"), or toggle it in Settings → Tools → ComposeGuard.
ComposeGuard includes a tool window that tracks rule violations across your project.
- 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 View → Tool Windows → ComposeGuard, or click a ComposeGuard gutter icon.
Configure ComposeGuard at Settings → Tools → ComposeGuard.
- 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.
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.
| Key | Affects | Meaning |
|---|---|---|
compose_allowed_composable_function_names | ComposableNaming | Names (or regexes) exempt from the PascalCase/camelCase check |
compose_content_emitters | ContentEmission, MultipleContentEmitters, ModifierRequired, TrailingLambda | Extra composables that count as emitting UI |
compose_content_emitters_denylist | same as above | Composables that must never count as emitting UI |
compose_check_modifiers_for_visibility | ModifierRequired | only_public (default), public_and_internal, or all |
compose_modifier_missing_ignore_annotated | ModifierRequired | Annotation names whose composables are skipped |
compose_custom_modifiers | ModifierRequired, ModifierNaming, ModifierDefaultValue, ParameterOrdering, TrailingLambda | Extra types treated as Modifier (e.g. GlanceModifier) |
compose_treat_as_lambda | ParameterOrdering, TrailingLambda | Type aliases treated as plain lambdas |
compose_treat_as_composable_lambda | ParameterOrdering, TrailingLambda | Type aliases treated as @Composable content slots |
compose_view_model_factories | ExplicitDependencies | Extra ViewModel factory functions |
compose_allowed_composition_locals | ExplicitDependencies, CompositionLocalAllowlist | CompositionLocals that may be read or declared |
compose_allowed_state_holder_names | ViewModelForwarding | Type name regexes that are not treated as forwarded ViewModels |
compose_allowed_forwarding | ViewModelForwarding | Composables a ViewModel may be forwarded to |
compose_allowed_forwarding_of_types | ViewModelForwarding | ViewModel types that may be forwarded |
compose_allowed_from_m2 | Material2Usage | Material 2 imports (or package prefixes) that are allowed |
compose_allowed_lambda_parameter_names | EventParameterNaming | Event parameter names exempt from the present-tense check |
compose_preview_naming_strategy | PreviewNaming | anywhere (default), suffix, or prefix |
compose_composable_nesting_depth_threshold | ComposableNestingDepth | Maximum nesting depth (default 3) |
compose_disallow_material2, compose_disallow_unstable_collections, compose_preview_naming_enabled, compose_composable_nesting_depth_enabled | rule enablement | true 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 = 4Adding the plugin to a large legacy project? Roll it out gradually instead of facing every warning at once:
- Start with the Stricter category off (
Material2Usage,UnstableCollections). - Enable categories one at a time as you refactor — the category checkbox toggles the whole group.
- Use
@Suppress("<RuleId>")for individual, intentional exceptions.
- IntelliJ IDEA 2024.2+ or Android Studio Ladybug (2024.2)+
- The bundled Kotlin plugin (enabled by default)
| ComposeGuard | Supported IDE builds |
|---|---|
| 1.2.x | 2024.2 – 2026.2 |
Contributions are welcome — issues and pull requests both.
- Fork the repository.
- Create a feature branch:
git checkout -b feature/amazing-feature. - Make your change and add tests (
./gradlew :compose-guard:test). - Commit and push, then open a Pull Request.
Built on the excellent Compose Rules guidelines by Nacho Lopez (mrmans0n).
Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🤩
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


