compose-modifier-and-layout-style
Use when writing or reviewing Jetpack Compose layout APIs, modifier parameters, modifier chain construction, hardcoded root layout decisions, or layout wrappers around a single conditional.
What this skill does
# Compose modifier and layout style
## Core principle
A composable that emits layout is a leaf the *parent* places — the parent decides position, size, alignment, padding. The composable's job is structure (what's inside), not placement (where it goes). Three rules follow:
- **Declare a `modifier` parameter and apply it to the root**, so the parent can actually do its job. Hardcoding `.fillMaxWidth()` on a composable's root takes that decision away from every future caller.
- **Construct modifier chains as one fluent expression**, not stepwise reassignments. Both compile to the same thing, but the chain *reads* as intent in one pass.
- **Conditional rendering belongs where the condition applies.** A layout call whose only content is one `if` exists solely to hold the condition — push the `if` outside instead.
These travel together because the same composable usually triggers all three: you declare its parameters (rule 1), the caller constructs a chain to position it (rules 2), and the body has a conditional you might be tempted to wrap (rule 3).
## When to use this skill
- You're writing a `@Composable fun` that calls a layout (`Box`, `Column`, `Row`, `LazyColumn`, `Text`, `Image`, `Surface`, `Card`, `Layout { … }`, anything from `compose.foundation.layout` or `compose.material*`) and its signature has no `modifier` parameter, or has one that isn't applied to the root, or has a hardcoded `.fillMaxWidth()`/`.padding(...)` on the root.
- You see `var m = Modifier` followed by `m = m.padding(…)`, `m = m.background(…)`, etc.
- A `modifier = …` argument has three or more chained calls on a single line.
- A composable's body is `Layout { if (cond) Content() }` — one conditional, nothing else.
## 1. Declare a `modifier` parameter
For composables that emit layout, prefer a `modifier` parameter after required parameters and before content/lambda parameters, with a default of `Modifier`. The name is exactly `modifier` — not `mod`, not `m`, not `wrapperModifier`.
```kotlin
// ❌ BAD — no modifier param; caller can't position, size, or constrain this
@Composable
fun HomeScreenHeader(title: String, subtitle: String) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(title, style = MaterialTheme.typography.headlineLarge)
Text(subtitle, style = MaterialTheme.typography.bodyMedium)
}
}
```
```kotlin
// ✅ GOOD — parent decides width and padding; the composable describes structure only
@Composable
fun HomeScreenHeader(
title: String,
subtitle: String,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(title, style = MaterialTheme.typography.headlineLarge)
Text(subtitle, style = MaterialTheme.typography.bodyMedium)
}
}
```
The caller now writes `HomeScreenHeader(title, subtitle, Modifier.fillMaxWidth().padding(horizontal = 16.dp))` once, at the home screen — the only place that knows the layout actually wants those.
## 2. Apply the caller's modifier to the root, and apply it first
When the root layout already takes other arguments (alignment, arrangement, padding *that's intrinsic to the composable*), the caller-provided modifier still goes on the root layout's `modifier` parameter — and the composable's local chain is appended after.
```kotlin
// ❌ BAD — modifier accepted but never applied
@Composable
fun Avatar(url: String, modifier: Modifier = Modifier) {
Image(painter = rememberAsyncImagePainter(url), contentDescription = null)
}
// ❌ BAD — applied to a child, not the root; caller's size/position changes don't take
@Composable
fun Avatar(url: String, modifier: Modifier = Modifier) {
Box {
Image(
painter = rememberAsyncImagePainter(url),
contentDescription = null,
modifier = modifier,
)
}
}
// ❌ BAD — caller's modifier ends up last, so the composable's own size wins
@Composable
fun Avatar(url: String, modifier: Modifier = Modifier) {
Image(
painter = rememberAsyncImagePainter(url),
contentDescription = null,
modifier = Modifier
.clip(CircleShape)
.size(48.dp)
.then(modifier),
)
}
```
```kotlin
// ✅ GOOD — caller's modifier first, then the composable's intrinsic chain
@Composable
fun Avatar(url: String, modifier: Modifier = Modifier) {
Image(
painter = rememberAsyncImagePainter(url),
contentDescription = null,
modifier = modifier
.clip(CircleShape)
.size(48.dp),
)
}
```
Order matters: in a modifier chain, the *earlier* segment is the outer wrapper. The caller's modifier should be the outermost so caller-provided `.size(...)` or `.padding(...)` can override the composable's defaults rather than being overridden by them.
## 3. Don't hardcode layout decisions on the root
If the composable's root has `.fillMaxWidth()`, `.padding(horizontal = 16.dp)`, `.height(56.dp)`, etc., the caller can't *not* have them. Those are layout choices the parent should own.
```kotlin
// ❌ BAD — every caller now fills max width whether they want to or not
@Composable
fun PrimaryButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
Button(
onClick = onClick,
modifier = modifier.fillMaxWidth(), // ← hardcoded
) { Text(text) }
}
// ✅ GOOD — caller adds .fillMaxWidth() if (and only if) they want it
@Composable
fun PrimaryButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
Button(onClick = onClick, modifier = modifier) { Text(text) }
}
```
The carve-out is for modifiers that are part of the **identity** of the composable — what makes an `Avatar` an avatar (the `.clip(CircleShape)` and a default `.size(48.dp)`), not where it sits on the screen. Test: can you imagine a caller wanting a version of this composable *without* that modifier? If yes, push it out. If no (an avatar without `clip(CircleShape)` isn't an avatar), keep it — but put it *after* the caller's modifier in the chain (see §2).
## 4. Construct modifier chains as one fluent expression
Recomposition re-runs the composable body — every modifier expression is re-evaluated. Reassigning `var modifier =` step-by-step looks plausible but breaks the visual flow, invites further mutation, and produces nothing a chain doesn't.
```kotlin
// ❌ BAD — visual flow broken into reassignments; `var` invites more mutation
@Composable
fun Demo() {
var m = Modifier
m = m.padding(16.dp)
m = m.fillMaxSize()
Box(m) { }
}
// ❌ ALSO BAD — same shape, dressed up with .then()
@Composable
fun Demo() {
var m = Modifier
m = m.padding(16.dp)
m = m.then(Modifier.fillMaxSize())
Box(m) { }
}
```
```kotlin
// ✅ GOOD
@Composable
fun Demo() {
val m = Modifier
.padding(16.dp)
.fillMaxSize()
Box(m) { }
}
```
`val`, not `var`: once the chain is built, nothing should re-bind it. The reassignment shape is what makes `var` look necessary; the chain shape doesn't need it.
### Inline at the call site is fine for short chains
For one or two calls, build the modifier inline. The "extract to a `val`" rule only earns its keep when the chain is long enough to be worth naming, or when the same chain repeats.
```kotlin
// ✅ GOOD — short chain inline
Box(modifier = Modifier.fillMaxWidth()) { … }
Box(modifier = Modifier.padding(8.dp).background(Color.Red)) { … }
```
### Conditional segments stay on the chain
A common reason to reach for `var` is "the modifier depends on a condition." It doesn't — splice the condition inline:
```kotlin
// ✅ GOOD — conditional inside the chain, still one expression
Box(
modifier = Modifier
.fillMaxWidth()
.then(if (selected) Modifier.background(Color.Red) else Modifier),
)
```
`Modifier` (the empty modifier) is theRelated in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing — use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.