If your Jetpack Compose app supports light and dark themes, you’ve probably noticed the default behavior: a sudden cut between color schemes when the system UI mode changes.
I think we’ve all seen theme changes that look like this:
We can do better.
By wrapping your theme setup with some animation, we can achieve smooth transitions between light and dark themes that feel much more polished — without rebuilding your entire app structure.
The Idea: Animate the ColorScheme
Jetpack Compose’s MaterialTheme
accepts a ColorScheme
. If we gradually animate the colors inside that scheme when the system toggles between dark and light, we can fade the colors smoothly across the entire app.
Here’s a simplified example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
You can expand this to include all 20+ color swatches if you want — but we’ll show a better way shortly.
The Problem: Too Many Animations
Manually animating every color swatch with animateColorAsState
works, but:
- It’s verbose
- It runs a lot of recompositions (even for unused swatches)
- It can be hard to keep up if
ColorScheme
changes
A Better Approach: Animating with updateTransition
A cleaner solution is to animate all properties as part of a single Transition
. That way, Compose shares the animation clock and easing, and you’re not scattering a dozen independent animation calls.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
This version:
- Uses a single
Transition
to group related animations - Shares the same easing/duration across all swatches
- Is easier to extend or modify incrementally
After we add our animations, it will look more like this:
Final Notes
- If you want to animate all color properties, you’ll still need to call
animateColor
for each one — but this pattern scales better than copy-pasting 25animateColorAsState
calls. - For most apps, animating just a handful of swatches (
primary
,background
,surface
,onPrimary
,error
) gives most of the perceived polish. - Avoid using reflection or deep copying here — it’s better to be explicit and in control of what’s animated.
Wrap-Up
This technique brings a much smoother feel to your app, especially if your users frequently switch between light and dark mode (or you support a manual toggle).
(Hopefully the Compose team eventually provides a helper for this boilerplate.)
Happy theming!
Sample Code
Want to try it out yourself? I’ve published a small sample project on GitHub that demonstrates both animated and non-animated theme switching:
View the sample project on GitHub
It includes the exact code from this post and the toggle UI shown in the GIFs.