What You Will Build

A card that flips around its X axis with a 3D perspective when tapped, revealing different content on the back. The flip uses graphicsLayer with rotationX and a camera distance setting to create a convincing 3D effect. This pattern is used for flashcard apps, profile cards, and info-reveal interactions.

Why This Pattern Matters

3D card flips add a spatial dimension to flat interfaces. The key challenge is rendering the correct face based on rotation angle and counter-rotating the back face so its text reads correctly. Compose's graphicsLayer makes this straightforward.

The Flip Card Implementation

@Composable
fun FlipCardScreen() {
    val cards = listOf(
        Triple("Creativity", "Think outside the box", Color(0xFF6366F1)),
        Triple("Innovation", "Build the future today", Color(0xFFEC4899)),
        Triple("Excellence", "Pursue perfection always", Color(0xFF10B981))
    )

    Column(
        modifier = Modifier.fillMaxSize().padding(24.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        cards.forEach { (front, back, color) ->
            var flipped by remember { mutableStateOf(false) }
            val rotation by animateFloatAsState(
                if (flipped) 180f else 0f,
                tween(600),
                label = "flip"
            )

            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(140.dp)
                    .padding(vertical = 8.dp)
                    .graphicsLayer {
                        rotationX = rotation
                        cameraDistance = 12f * density
                    }
                    .clickable { flipped = !flipped },
                contentAlignment = Alignment.Center
            ) {
                if (rotation <= 90f) {
                    // Front face
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        shape = RoundedCornerShape(20.dp),
                        color = color,
                        shadowElevation = 6.dp
                    ) {
                        Box(contentAlignment = Alignment.Center) {
                            Text(front, color = Color.White,
                                fontSize = 26.sp,
                                fontWeight = FontWeight.Bold)
                        }
                    }
                } else {
                    // Back face (counter-rotated)
                    Surface(
                        modifier = Modifier
                            .fillMaxSize()
                            .graphicsLayer { rotationX = 180f },
                        shape = RoundedCornerShape(20.dp),
                        color = color.copy(alpha = 0.8f),
                        shadowElevation = 6.dp
                    ) {
                        Box(contentAlignment = Alignment.Center) {
                            Text(back, color = Color.White,
                                fontSize = 18.sp)
                        }
                    }
                }
            }
        }
    }
}

Tips and Pitfalls

  • cameraDistance = 12f * density prevents the card from looking warped during rotation. Lower values create a fisheye effect.
  • The 90-degree threshold determines when to swap front and back content. At exactly 90 degrees the card is edge-on and invisible.
  • Counter-rotate the back face with graphicsLayer { rotationX = 180f } so text is not mirrored.
  • Use tween(600) for a smooth flip. Faster than 400ms feels abrupt; slower than 800ms feels sluggish.

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.