Customizing Material Components with Gradients

▶ Watch (0:08)

Jet Snack, the first Compose example app, used a custom design system with gradients on filter chips and buttons. Material components accept a single Color for backgrounds, not a Brush gradient. Developers often wrap a chip in a Box with a gradient background, manually handle shape and press states, then fork material source. Forks lose material defaults: accessibility, RTL positioning, interaction states. The new Styles API solves this by separating form from function and adding state-based styling and performance benefits.

What a Style Is and How to Use It

▶ Watch (3:53)

A style is an interface that defines a component’s appearance, implemented as a specialized modifier. Styles do not replace modifiers. They replace visual-property parameters. To make a custom button stylable, add a stylable modifier and a style parameter. Styles have four fundamentals: properties override each other (last defined wins), properties inherit from parent to child, styles are reusable (define once, use on many components), and styles are mergeable. Merging combines two styles; when same property appears twice, the later style’s value wins.

State-Based Styling and Animations

▶ Watch (13:16)

The API includes built-in states: default, pressed, hovered, and focused. Hovered and focused activate on mouse-equipped devices or TV D-pads. Add a state block inside the style lambda to change look per state, for example a light purple background on press. Without animations, the transition snaps. Surround any property with an animate lambda for automatic tween between states. Pass a custom animation spec such as tween with longer duration to slow the effect. This eliminates manual Animatable state variables.

Custom Style States and Performance Gains

▶ Watch (16:33)

The StyleState class stores interaction states and supports custom states via a map-like get operator. In Jet Snack, the add-to-cart button needed loading, loaded, and error visuals. A custom enum, a StyleStateKey, and extension functions on StyleScope enable the button to read and react to loading state. Restricting extension functions to a custom LoadingStyle scope makes invalid states a compile-time error. Performance improves because styles use a single modifier, skip composition when only draw changes, and defer animation creation. A border benchmark showed 70% fewer allocations and up to 2.3x faster rendering. A material button prototype cut allocations 37% and rendered 1.6x faster.

Integrating Styles into Themes and Media Queries

▶ Watch (26:44)

Add a component-defaults style layer to the theme, accessing color, shape, and typography tokens. The button composable resolves styles in a precedence hierarchy: incoming style parameter, themed default, inherited parent styles. For dark/light mode, styles automatically adapt because they read composition locals. Material 3 components will expose style parameters in a future release. The experimental MediaQuery API returns window characteristics as booleans; styles re-evaluate when the condition changes. For example, a button can shrink padding and size on devices with a mouse connected.

Notable Quotes

Styles are not here to replace modifiers, but instead here to replace parameters that are related to visual properties. Meghan Mehta · ▶ Watch (4:09)

In this benchmark, styles had approximately 70% fewer allocations and used up to 2.3 times faster to render the next pixel. Chuck Jazdzewski · ▶ Watch (24:56)

When a layout property such as content padding changes, only layout and draw run. If a draw property such as background changes, only the draw phase runs. Chuck Jazdzewski · ▶ Watch (25:10)

Choose a style when you want to override default existing the defaults for an existing component, perform high performance interactive state animations, or to define themewide sets of properties for a component. Chuck Jazdzewski · ▶ Watch (34:59)

Key Takeaways

  • Styles replace visual parameters with a single, reusable style modifier.
  • State-based styling and animations are built in, requiring no extra state variables.
  • Custom style states limit extension functions to components that support them.
  • Styles reduce allocations and render faster than equivalent modifier chains.
  • MediaQuery unlocks adaptive themes across device form factors.