What You Will Build
A neumorphic (soft UI) design screen featuring raised and inset elements that appear to be extruded from the background surface. Neumorphism creates depth through paired light and dark shadows on a monochrome surface, giving buttons and cards a tactile, almost physical appearance.
Why Neumorphism Is Interesting for Compose
While Material Design 3 uses tonal elevation, neumorphism creates depth differently: a light shadow on one side (simulating a light source) and a dark shadow on the opposite side. In Compose, you can achieve this with drawBehind to draw offset shadows manually, which is a powerful custom drawing technique.
Key Compose Concepts
- Modifier.drawBehind for drawing custom shadows behind the composable.
- drawCircle with offset centers for paired light/dark shadow simulation.
- Consistent background color across the surface and elements (typically a light gray like
#E0E5EC).
Step 1: The Neumorphic Background
Neumorphism requires a single consistent background color. Everything is the same hue; depth comes only from shadows:
val neuBackground = Color(0xFFE0E5EC)
Column(
modifier = Modifier
.fillMaxSize()
.background(neuBackground)
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
"Neumorphism",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF2D3436)
)
}
Step 2: Create the Raised Circle with Custom Shadows
The key technique is using drawBehind with two offset circles - a white one for the light side and a dark one for the shadow side:
Box(
modifier = Modifier
.size(150.dp)
.clip(CircleShape)
.background(Color(0xFFE0E5EC))
.drawBehind {
// Light shadow (upper-left)
drawCircle(
color = Color.White.copy(alpha = 0.7f),
radius = size.minDimension / 2,
center = center + Offset(-4f, -4f)
)
// Dark shadow (lower-right)
drawCircle(
color = Color(0xFFA3B1C6).copy(alpha = 0.5f),
radius = size.minDimension / 2,
center = center + Offset(4f, 4f)
)
},
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Fingerprint,
contentDescription = null,
modifier = Modifier.size(60.dp),
tint = Color(0xFF6C5CE7)
)
}
Step 3: Build a Neumorphic Card
For rectangular elements, use Card with the same background color and gentle elevation:
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFFE0E5EC)
),
elevation = CardDefaults.cardElevation(
defaultElevation = 8.dp
)
) {
Column(Modifier.padding(20.dp)) {
Text("Neumorphic Card",
fontWeight = FontWeight.Bold,
color = Color(0xFF2D3436))
Text("Soft UI design style",
color = Color(0xFF636E72))
}
}
Tips and Pitfalls
- Shadow offset consistency: Always use the same direction for light (upper-left) and dark (lower-right) to maintain a consistent light source.
- Avoid bright colors: Neumorphism works best with muted, desaturated palettes. Bright accents should be small (icon tints, text highlights).
- Accessibility concern: Low contrast between elements and background can be difficult for visually impaired users. Consider providing a high-contrast mode.
- drawBehind runs on every frame during recomposition, so keep the drawing operations simple for smooth performance.
Minimum SDK: API 24+ with Compose BOM 2024.01+