跪拜 Guibai
← All articles
Android · Kotlin

Jetpack Compose's New Styles API: A Declarative Alternative to Modifier Chains

By RockByte ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

For Android developers building custom Compose components or design systems, the Styles API offers a cleaner, more performant way to manage appearance — especially state-driven styles and animations. It signals a shift toward a CSS-like, declarative styling model that could become the standard in future Compose versions, reducing the need for verbose `Modifier` chains and manual state-to-color mappings.

Summary

Jetpack Compose's new Styles API, currently experimental in foundation alpha 1.12.0-alpha03, introduces a `Style` interface and `Modifier.styleable` to consolidate UI appearance rules. Instead of spreading padding, colors, shapes, and state-dependent styles across multiple `Modifier` calls and component parameters, developers define them inside a `Style` lambda or a reusable `Style` object. The API covers layout, visual appearance, transforms, and typography, with typography properties supporting parent-to-child inheritance. Styles are not additive — the last setting wins — and multiple styles can be merged with the `then` operator.

A key feature is built-in interaction state handling: `StyleState` tracks `pressed`, `hovered`, `focused`, `selected`, `enabled`, and `toggled` states, allowing conditional styles directly inside the `Style` block. State transitions support animations via `animate()` with custom `animationSpec` (tween, spring). Developers can also define custom states using `StyleStateKey` and extension functions. The API aims to reduce boilerplate, improve performance by executing styles during Draw/Layout instead of Composition, and make component APIs cleaner by replacing dozens of parameters with a single `Style` parameter.

Takeaways
Styles API is experimental in Compose foundation 1.12.0-alpha03 and subject to change.
Use `Style` parameter on components or `Modifier.styleable` for components without a style parameter.
Style properties are not additive — the last set value overrides previous ones.
Multiple `Style` objects can be merged with the `then` operator, with the rightmost overriding.
Typography and coloring properties (e.g., `contentColor`, `fontSize`) are inherited by child `Text` components.
Built-in interaction states: pressed, hovered, focused, selected, enabled, toggled — accessible via `StyleState`.
State-based animations use `animate()` inside state blocks, supporting tween and spring specs.
Custom states can be defined with `StyleStateKey` and extension functions on `MutableStyleState`.
Styles execute during Draw/Layout phases, skipping Composition, reducing recomposition.
`Modifier` remains necessary for interactions, custom drawing, and behaviors not covered by `Style`.
Conclusions

The Styles API directly addresses the pain of managing dozens of style parameters on complex components — a common complaint in large Compose codebases.

By moving style resolution to Draw/Layout, the API tackles a real performance issue: recomposition triggered by `animateColorAsState` or state-driven color changes.

The inheritance model for typography properties mirrors CSS, making it intuitive for developers with web backgrounds and enabling design-system-level theming.

Styles are not a replacement for `Modifier` but a targeted solution for appearance — the API draws a clearer line between layout/interaction and styling.

The ability to define custom states (e.g., media player states) opens the door to domain-specific styling patterns beyond standard UI interactions.

The API's experimental status and lack of Material Design support mean early adopters should limit usage to custom components and design systems, not production Material widgets.

The declarative, state-encapsulated style blocks feel like a natural evolution toward a more DSL-like Compose, reducing the imperative feel of `Modifier` chains.

Concepts & terms
Style
An interface in Jetpack Compose that groups UI appearance properties (padding, background, border, shape, etc.) into a single declarative block, similar to a CSS class.
StyleScope
The receiver scope inside a `Style` lambda that provides functions like `background()`, `padding()`, `border()`, and access to the current `StyleState` for conditional styling.
StyleState
A read-only interface that tracks an element's interaction state (e.g., `isPressed`, `isHovered`, `isEnabled`) and allows custom states, used to apply state-dependent styles.
Modifier.styleable
An extension on `Modifier` that applies a `Style` to any composable, even those without a built-in `style` parameter, optionally with a `StyleState` for state-based styling.
StyleStateKey
A key used to define custom states in `StyleState`, enabling domain-specific state-driven styles beyond the built-in interaction states.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗