Las sombras elevan visualmente la IU, indican interactividad a los usuarios y proporcionan comentarios inmediatos sobre las acciones del usuario. Compose proporciona varias formas de incorporar sombras en tu app:
Modifier.shadow()
: Crea una sombra basada en la elevación detrás de un elemento componible que cumple con los lineamientos de Material Design.Modifier.dropShadow()
: Crea una sombra personalizable que aparece detrás de un elemento componible, lo que hace que parezca elevado.Modifier.innerShadow()
: Crea una sombra dentro de los bordes de un elemento componible, lo que hace que parezca presionado en la superficie detrás de él.
Modifier.shadow()
es adecuado para crear sombras básicas, mientras que los modificadores dropShadow
y innerShadow
ofrecen un control y una precisión más detallados sobre la renderización de sombras.
En esta página, se describe cómo implementar cada uno de estos modificadores, incluida la forma de animar sombras cuando el usuario interactúa y cómo encadenar los modificadores innerShadow()
y dropShadow()
para crear sombras de gradiente, sombras neumórficas y mucho más.
Cómo crear sombras básicas
Modifier.shadow()
crea una sombra básica según los lineamientos de Material Design que simula una fuente de luz desde arriba. La profundidad de la sombra se basa en un valor elevation
, y la sombra proyectada se recorta según la forma del elemento componible.
@Composable fun ElevationBasedShadow() { Box( modifier = Modifier.aspectRatio(1f).fillMaxSize(), contentAlignment = Alignment.Center ) { Box( Modifier .size(100.dp, 100.dp) .shadow(10.dp, RectangleShape) .background(Color.White) ) } }

Modifier.shadow
.Implementa sombras paralelas
Usa el modificador dropShadow()
para dibujar una sombra precisa detrás de tu contenido, lo que hace que el elemento parezca elevado.
Puedes controlar los siguientes aspectos clave a través de su parámetro Shadow
:
radius
: Define la suavidad y la difusión del desenfoque.color
: Define el color del tinte.offset
: Posiciona la geometría de la sombra a lo largo de los ejes X e Y.spread
: Controla la expansión o contracción de la geometría de la sombra.
Además, el parámetro shape
define la forma general de la sombra. Puede usar cualquier geometría del paquete androidx.compose.foundation.shape
, así como las formas expresivas de Material.
Para implementar una sombra paralela básica, agrega el modificador dropShadow()
a tu cadena de elementos componibles y proporciona el radio, el color y la propagación. Ten en cuenta que el fondo purpleColor
que aparece sobre la sombra se dibuja después del modificador dropShadow()
:
@Composable fun SimpleDropShadowUsage() { Box(Modifier.fillMaxSize()) { Box( Modifier .width(300.dp) .height(300.dp) .dropShadow( shape = RoundedCornerShape(20.dp), shadow = Shadow( radius = 10.dp, spread = 6.dp, color = Color(0x40000000), offset = DpOffset(x = 4.dp, 4.dp) ) ) .align(Alignment.Center) .background( color = Color.White, shape = RoundedCornerShape(20.dp) ) ) { Text( "Drop Shadow", modifier = Modifier.align(Alignment.Center), fontSize = 32.sp ) } } }
Puntos clave sobre el código
- El modificador
dropShadow()
se aplica al elementoBox
interno. La sombra tiene las siguientes características:- Una forma de rectángulo redondeado (
RoundedCornerShape(20.dp)
) - Un radio de desenfoque de
10.dp
, que hace que los bordes sean suaves y difusos - Una propagación de
6.dp
, que expande el tamaño de la sombra y la hace más grande que la caja que la proyecta - Un valor alfa de
0.5f
, que hace que la sombra sea semitransparente
- Una forma de rectángulo redondeado (
- Después de definir la sombra, el objeto .Se aplica el modificador
background()
.- El
Box
está relleno de color blanco. - El fondo se recorta con la misma forma de rectángulo redondeado que la sombra.
- El
Resultado

Implementa sombras interiores
Para crear un efecto inverso a dropShadow
, usa Modifier.innerShadow()
, que crea la ilusión de que un elemento está empotrado o presionado en la superficie subyacente.
El orden es importante cuando se crean sombras interiores. La sombra interior se dibuja sobre el contenido, por lo que, por lo general, debes hacer lo siguiente:
- Dibuja el contenido de fondo.
- Aplica el modificador
innerShadow()
para crear la apariencia cóncava.
Si el innerShadow()
se coloca antes del fondo, el fondo se dibuja sobre la sombra y la oculta por completo.
En el siguiente ejemplo, se muestra una aplicación de innerShadow()
en un RoundedCornerShape
:
@Composable fun SimpleInnerShadowUsage() { Box(Modifier.fillMaxSize()) { Box( Modifier .width(300.dp) .height(200.dp) .align(Alignment.Center) // note that the background needs to be defined before defining the inner shadow .background( color = Color.White, shape = RoundedCornerShape(20.dp) ) .innerShadow( shape = RoundedCornerShape(20.dp), shadow = Shadow( radius = 10.dp, spread = 2.dp, color = Color(0x40000000), offset = DpOffset(x = 6.dp, 7.dp) ) ) ) { Text( "Inner Shadow", modifier = Modifier.align(Alignment.Center), fontSize = 32.sp ) } } }

Modifier.innerShadow()
en un rectángulo con esquinas redondeadas.Anima las sombras en la interacción del usuario
Para que las sombras respondan a las interacciones del usuario, puedes integrar propiedades de sombras con las APIs de animación de Compose. Cuando un usuario presiona un botón, por ejemplo, la sombra puede cambiar para proporcionar comentarios visuales instantáneos.
El siguiente código crea un efecto de "presionado" con una sombra (la ilusión de que la superficie se empuja hacia abajo en la pantalla):
@Composable fun AnimatedColoredShadows() { SnippetsTheme { Box(Modifier.fillMaxSize()) { val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() // Create transition with pressed state val transition = updateTransition( targetState = isPressed, label = "button_press_transition" ) fun <T> buttonPressAnimation() = tween<T>( durationMillis = 400, easing = EaseInOut ) // Animate all properties using the transition val shadowAlpha by transition.animateFloat( label = "shadow_alpha", transitionSpec = { buttonPressAnimation() } ) { pressed -> if (pressed) 0f else 1f } // ... val blueDropShadow by transition.animateColor( label = "shadow_color", transitionSpec = { buttonPressAnimation() } ) { pressed -> if (pressed) Color.Transparent else blueDropShadowColor } // ... Box( Modifier .clickable( interactionSource, indication = null ) { // ** ...... **// } .width(300.dp) .height(200.dp) .align(Alignment.Center) .dropShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 10.dp, spread = 0.dp, color = blueDropShadow, offset = DpOffset(x = 0.dp, -(2).dp), alpha = shadowAlpha ) ) .dropShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 10.dp, spread = 0.dp, color = darkBlueDropShadow, offset = DpOffset(x = 2.dp, 6.dp), alpha = shadowAlpha ) ) // note that the background needs to be defined before defining the inner shadow .background( color = Color(0xFFFFFFFF), shape = RoundedCornerShape(70.dp) ) .innerShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 8.dp, spread = 4.dp, color = innerShadowColor2, offset = DpOffset(x = 4.dp, 0.dp) ) ) .innerShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 20.dp, spread = 4.dp, color = innerShadowColor1, offset = DpOffset(x = 4.dp, 0.dp), alpha = innerShadowAlpha ) ) ) { Text( "Animated Shadows", // ... ) } } } }
Puntos clave sobre el código
- Declara los estados inicial y final de los parámetros que se animarán al presionar con
transition.animateColor
ytransition.animateFloat
. - Usa
updateTransition
y le proporciona eltargetState (targetState = isPressed)
elegido para verificar que todas las animaciones estén sincronizadas. Cada vez que cambiaisPressed
, el objeto de transición administra automáticamente la animación de todas las propiedades secundarias desde sus valores actuales hasta los nuevos valores objetivo. - Define la especificación
buttonPressAnimation
, que controla la sincronización y la aceleración de la transición. Especifica untween
(abreviatura de in-between) con una duración de 400 milisegundos y una curva deEaseInOut
, lo que significa que la animación comienza lentamente, se acelera en el medio y se ralentiza al final. - Define un
Box
con una cadena de funciones de modificadores que aplican todas las propiedades animadas para crear el elemento visual, incluidas las siguientes:- .
clickable()
: Es un modificador que hace que el elementoBox
sea interactivo. .dropShadow()
: Primero se aplican dos sombras paralelas exteriores. Sus propiedades de color y alfa están vinculadas a los valores animados (blueDropShadow
, etc.) y crean la apariencia inicial elevada..innerShadow()
: Se dibujan dos sombras internas sobre el fondo. Sus propiedades están vinculadas al otro conjunto de valores animados (innerShadowColor1
, etc.) y crean la apariencia con sangría.
- .
Resultado
Cómo crear sombras con gradiente
Las sombras no se limitan a colores sólidos. La API de sombras acepta un Brush
, que te permite crear sombras de gradiente.
Box( modifier = Modifier .width(240.dp) .height(200.dp) .dropShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 10.dp, spread = animatedSpread.dp, brush = Brush.sweepGradient( colors ), offset = DpOffset(x = 0.dp, y = 0.dp), alpha = animatedAlpha ) ) .clip(RoundedCornerShape(70.dp)) .background(Color(0xEDFFFFFF)), contentAlignment = Alignment.Center ) { Text( text = breathingText, color = Color.Black, style = MaterialTheme.typography.bodyLarge ) }
Puntos clave sobre el código
dropShadow()
agrega una sombra detrás de la caja.brush = Brush.sweepGradient(colors)
colorea la sombra con un gradiente que rota a través de una lista decolors
predefinidos, lo que crea un efecto similar al arcoíris.
Resultado
Puedes usar un pincel como sombra para crear un gradiente dropShadow()
con una animación de "respiración":
Cómo combinar sombras
Puedes combinar y superponer los modificadores dropShadow()
y innerShadow()
para crear una variedad de efectos. En las siguientes secciones, se muestra cómo producir sombras neumórficas, neobrutalistas y realistas con esta técnica.
Cómo crear sombras neumórficas
Las sombras neumórficas se caracterizan por una apariencia suave que emerge de forma orgánica del fondo. Para crear sombras neumórficas, haz lo siguiente:
- Usa un elemento que comparta los mismos colores que su fondo.
- Aplica dos sombras paralelas tenues y opuestas: una sombra clara en una esquina y una sombra oscura en la esquina opuesta.
En el siguiente fragmento, se superponen dos modificadores dropShadow()
para crear el efecto neumórfico:
@Composable fun NeumorphicRaisedButton( shape: RoundedCornerShape = RoundedCornerShape(30.dp) ) { val bgColor = Color(0xFFe0e0e0) val lightShadow = Color(0xFFFFFFFF) val darkShadow = Color(0xFFb1b1b1) val upperOffset = -10.dp val lowerOffset = 10.dp val radius = 15.dp val spread = 0.dp Box( modifier = Modifier .fillMaxSize() .background(bgColor) .wrapContentSize(Alignment.Center) .size(240.dp) .dropShadow( shape, shadow = Shadow( radius = radius, color = lightShadow, spread = spread, offset = DpOffset(upperOffset, upperOffset) ), ) .dropShadow( shape, shadow = Shadow( radius = radius, color = darkShadow, spread = spread, offset = DpOffset(lowerOffset, lowerOffset) ), ) .background(bgColor, shape) ) }

Cómo crear sombras neobrutalistas
El estilo neobrutalista muestra diseños en bloque de alto contraste, colores vívidos y bordes gruesos. Para crear este efecto, usa un dropShadow()
con desenfoque cero y una compensación distinta, como se muestra en el siguiente fragmento:
@Composable fun NeoBrutalShadows() { SnippetsTheme { val dropShadowColor = Color(0xFF007AFF) val borderColor = Color(0xFFFF2D55) Box(Modifier.fillMaxSize()) { Box( Modifier .width(300.dp) .height(200.dp) .align(Alignment.Center) .dropShadow( shape = RoundedCornerShape(0.dp), shadow = Shadow( radius = 0.dp, spread = 0.dp, color = dropShadowColor, offset = DpOffset(x = 8.dp, 8.dp) ) ) .border( 8.dp, borderColor ) .background( color = Color.White, shape = RoundedCornerShape(0.dp) ) ) { Text( "Neobrutal Shadows", modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyMedium ) } } } }

Crea sombras realistas
Las sombras realistas imitan las sombras del mundo físico: parecen iluminadas por una fuente de luz principal, lo que genera una sombra directa y una sombra más difusa. Puedes apilar varias instancias de dropShadow()
y innerShadow()
con diferentes propiedades para recrear efectos de sombra realistas, como se muestra en el siguiente fragmento:
@Composable fun RealisticShadows() { Box(Modifier.fillMaxSize()) { val dropShadowColor1 = Color(0xB3000000) val dropShadowColor2 = Color(0x66000000) val innerShadowColor1 = Color(0xCC000000) val innerShadowColor2 = Color(0xFF050505) val innerShadowColor3 = Color(0x40FFFFFF) val innerShadowColor4 = Color(0x1A050505) Box( Modifier .width(300.dp) .height(200.dp) .align(Alignment.Center) .dropShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 40.dp, spread = 0.dp, color = dropShadowColor1, offset = DpOffset(x = 2.dp, 8.dp) ) ) .dropShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 4.dp, spread = 0.dp, color = dropShadowColor2, offset = DpOffset(x = 0.dp, 4.dp) ) ) // note that the background needs to be defined before defining the inner shadow .background( color = Color.Black, shape = RoundedCornerShape(100.dp) ) // // .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 12.dp, spread = 3.dp, color = innerShadowColor1, offset = DpOffset(x = 6.dp, 6.dp) ) ) .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 4.dp, spread = 1.dp, color = Color.White, offset = DpOffset(x = 5.dp, 5.dp) ) ) .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 12.dp, spread = 5.dp, color = innerShadowColor2, offset = DpOffset(x = (-3).dp, (-12).dp) ) ) .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 3.dp, spread = 10.dp, color = innerShadowColor3, offset = DpOffset(x = 0.dp, 0.dp) ) ) .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 3.dp, spread = 9.dp, color = innerShadowColor4, offset = DpOffset(x = 1.dp, 1.dp) ) ) ) { Text( "Realistic Shadows", modifier = Modifier.align(Alignment.Center), fontSize = 24.sp, color = Color.White ) } } }
Puntos clave sobre el código
- Se aplican dos modificadores
dropShadow()
encadenados con propiedades distintas, seguidos de un modificadorbackground
. - Los modificadores
innerShadow()
encadenados se aplican para crear el efecto de borde metálico alrededor del borde del componente.
Resultado
El fragmento de código anterior produce lo siguiente:
