Adding smooth motion to your Android app doesn’t just make it look “cool” — it guides the user’s eye and makes the interface feel responsive and alive. If you’ve ever felt overwhelmed by complex animation frameworks, I have great news: AnimatedVisibility in Jetpack Compose is here to do the heavy lifting for you.
In this guide, I’ll walk you through how to use AnimatedVisibility step by step. We’ll keep things simple, practical, and easy to follow. By the end, you’ll know how to create clean, engaging UI transitions without overcomplicating your code.
What is AnimatedVisibility?
AnimatedVisibility is a composable in Jetpack Compose that lets you show or hide UI elements with animation.
Instead of instantly appearing or disappearing, your UI components can:
- Fade in or out
- Slide in or out
- Expand or shrink
This creates a smoother and more natural user experience.
Why Use AnimatedVisibility?
Here’s why developers love using AnimatedVisibility:
- Makes UI feel modern and responsive
- Improves user experience with smooth transitions
- Easy to implement with minimal code
- Highly customizable animations
If you’re building dropdowns, alerts, expandable cards, or onboarding flows, AnimatedVisibility is incredibly useful.
Basic Example of AnimatedVisibility
Let’s start with a simple example.
Step 1: Add a Toggle State
var isVisible by remember { mutableStateOf(false) }This state controls whether the UI is visible or not.
Step 2: Use AnimatedVisibility
Column {
Button(onClick = { isVisible = !isVisible }) {
Text("Toggle Visibility")
}
AnimatedVisibility(visible = isVisible) {
Text("Hello! I appear with animation.")
}
}Here,
- When the button is clicked,
isVisiblechanges AnimatedVisibilityreacts to that change- The text appears or disappears with a default animation
By default, it uses a combination of fade and expand animations.

Adding Custom Animations
The real power of AnimatedVisibility comes from customization.
You can define how elements enter and exit.
Example: Fade + Slide Animation
AnimatedVisibility(
visible = isVisible,
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically()
) {
Text("Smooth animated text!")
}fadeIn()→ gradually appearsslideInVertically()→ slides from top or bottomfadeOut()→ fades awayslideOutVertically()→ slides out
You can combine animations using the + operator.
@Composable
fun FadePlusSlideExample() {
var isVisible by remember { mutableStateOf(false) }
Column {
Button(onClick = { isVisible = !isVisible }) {
Text("Toggle Visibility")
}
AnimatedVisibility(
visible = isVisible,
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically()
) {
Text("Smooth animated text!")
}
}
}
@Preview(showBackground = true)
@Composable
fun FadePlusSlideExamplePreview() {
CenteredPreview {
FadePlusSlideExample()
}
}
Controlling Animation Direction
You can customize how elements slide in.
slideInVertically { fullHeight -> -fullHeight }What This Means,
- The element enters from the top
-fullHeightmoves it above the screen before sliding down
Similarly, you can control exit direction:
slideOutVertically { fullHeight -> fullHeight }This makes it slide downward when disappearing.
@Composable
fun SlideDirectionExample() {
var isVisible by remember { mutableStateOf(false) }
Column {
Button(onClick = { isVisible = !isVisible }) {
Text("Toggle Visibility")
}
AnimatedVisibility(
visible = isVisible,
enter = fadeIn() + slideInVertically { fullHeight -> -fullHeight },
exit = fadeOut() + slideOutVertically { fullHeight -> fullHeight }
) {
Text("Slide Direction text!")
}
}
}
@Preview(showBackground = true)
@Composable
fun SlideDirectionExamplePreview() {
CenteredPreview {
SlideDirectionExample()
}
}
Using AnimatedVisibility with Expand and Shrink
This is great for dropdowns or expandable content.
@Composable
fun ExpandShrinkExample() {
var isExpandable by remember { mutableStateOf(false) }
Column {
Button(onClick = { isExpandable = !isExpandable }) {
Text(if (isExpandable) "Shrink Content" else "Expand Content")
}
Spacer(modifier = Modifier.height(16.dp))
AnimatedVisibility(
visible = isExpandable,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut(),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray)
.padding(16.dp)
) {
Text("Expandable content goes here")
}
}
}
}
@Preview(showBackground = true)
@Composable
fun ExpandShrinkExamplePreview() {
CenteredPreview {
ExpandShrinkExample()
}
}Why Use This?
- Feels natural for lists and cards
- Mimics real-world expansion behavior
- Works great for FAQs or settings screens
Real-World Use Case
Let’s combine everything into a practical example.
Expandable Card
@Composable
fun ExpandableCard() {
var expanded by remember { mutableStateOf(false) }
Column(modifier = Modifier.padding(16.dp)) {
Button(onClick = { expanded = !expanded }) {
Text("Show Details")
}
AnimatedVisibility(
visible = expanded,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Text(
text = "Here are more details about this item. This section expands smoothly.",
modifier = Modifier.padding(top = 8.dp)
)
}
}
}
@Preview(showBackground = true)
@Composable
fun ExpandableCardPreview() {
CenteredPreview {
ExpandableCard()
}
}- Clean separation of state and UI
- Smooth transition enhances usability
- Easy to reuse in different parts of your app
AnimatedVisibility with LazyColumn
Using AnimatedVisibility inside a LazyColumn is a great way to create smooth, modern list interactions. Think expandable list items, animated insert/remove, or showing extra details per row.
You’d typically combine AnimatedVisibility with LazyColumn when:
- Expanding/collapsing list items
- Showing extra details on click
- Animating conditional content inside rows
Here’s a simple example where each item expands when clicked.
Data Model
data class ListItem(
val id: Int,
val title: String,
val description: String
)Sample Data
val items = listOf(
ListItem(1, "Item 1", "This is item 1 details"),
ListItem(2, "Item 2", "This is item 2 details"),
ListItem(3, "Item 3", "This is item 3 details")
)LazyColumn with AnimatedVisibility
@Composable
fun ExpandableList() {
val expandedItems = remember { mutableStateListOf<Int>() }
LazyColumn {
items(items) { item ->
val isExpanded = expandedItems.contains(item.id)
Column(
modifier = Modifier
.fillMaxWidth()
.clickable {
if (isExpanded) {
expandedItems.remove(item.id)
} else {
expandedItems.add(item.id)
}
}
.padding(16.dp)
) {
Text(text = item.title)
AnimatedVisibility(
visible = isExpanded,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Text(
text = item.description,
modifier = Modifier.padding(top = 8.dp)
)
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun ExpandableListPreview() {
CenteredPreview {
ExpandableList()
}
}
Best Practices for Using AnimatedVisibility
To get the most out of AnimatedVisibility, keep these tips in mind:
1. Keep Animations Subtle
Avoid overly complex animations. Simple transitions feel more professional.
2. Use Meaningful Motion
Animations should guide the user, not distract them.
3. Manage State Properly
Use remember and mutableStateOf correctly to avoid unexpected behavior.
4. Combine Animations Carefully
Too many combined effects can feel heavy. Stick to 1–2 transitions.
5. Test on Real Devices
Animations may feel different on slower devices. Always test performance.
Common Mistakes to Avoid
Here are a few pitfalls when working with AnimatedVisibility:
- Forgetting to control state properly
- Overusing animations in every component
- Using heavy animations inside large lists
- Not handling re-composition efficiently
Keep things simple and intentional.
When Should You Use AnimatedVisibility?
Use AnimatedVisibility when you need to:
- Show/hide UI elements dynamically
- Create expandable layouts
- Improve onboarding screens
- Add feedback to user actions
- Build interactive components
If visibility changes are part of your UI, this composable is the right tool.
Conclusion
AnimatedVisibility is one of the easiest ways to bring life into your Jetpack Compose UI.
You don’t need complex animation frameworks or tons of code. With just a few lines, you can create smooth, engaging transitions that feel natural and polished.
Start small. Try a simple fade or slide. Then experiment with combinations as you get comfortable.
