Modifier.shake¶
Single-shot horizontal shake driven by a key. Re-fires when the key value changes.
When to use¶
- Form validation errors. Shake the input on a failed submit.
- Wrong-PIN, wrong-password feedback before the field clears.
When not to use¶
- Persistent state changes (use
colorPulseor a static error border for "stays wrong"). - During a typing or focus animation. Stack with
pressScaleonly on inert fields.
Usage¶
var error by remember { mutableStateOf<Throwable?>(null) }
OutlinedTextField(
value = text,
onValueChange = { text = it },
isError = error != null,
modifier = Modifier.shake(trigger = error),
)
Or with a counter you increment on each failure:
var failures by remember { mutableIntStateOf(0) }
Modifier.shake(trigger = failures)
Parameters¶
| Name | Type | Default | Notes |
|---|---|---|---|
trigger | Any? | required | Any value; a change re-fires the shake. Equal triggers do nothing. |
intensity | Dp | 8.dp | Peak horizontal displacement. |
durationMs | Int | 320 | Total shake duration. |
oscillations | Int | 3 | Number of full back-and-forth swings within durationMs. |
Design notes¶
- Node subtype:
LayoutModifierNode. Each frame we computesin(2π · oscillations · t) · intensity · dampingwheredamping = 1f - t, and apply the result asplaceRelative(x = offset). NoGraphicsLayerinvolved; pure layout-time horizontal shift. - Key-driven: the modifier compares the new
triggerto the old one insideupdate(). If they differ, fire. If they're equal, do nothing, even if the modifier is recreated for unrelated reasons. This is the "single-shot via state key" pattern. - Damped: the
1f - tfactor means the shake decays to zero rather than ending mid-swing. Cleaner than a hard cut-off.