Shadow (ombre) — un effet visuel dans les interfaces mobiles qui crée l'illusion de profondeur et de hiérarchie entre les éléments. Les ombres montrent quel élément est le plus haut dans le plan z et, par conséquent, est plus important ou interactif. Selon les Material Design Guidelines (Google, 2026), les ombres système utilisent cinq hauteurs clés (elevation) : 0 dp (plat), 1–4 dp (boutons), 6–12 dp (cartes), 16–24 dp (dialogues), 24+ dp (fenêtres modales). En savoir plus sur la hauteur des éléments dans l'article sur Elevation.
Points clés
L'ombre (shadow) dans les interfaces utilisateur est un effet visuel qui simule l'éclairage et la profondeur. Une ombre est créée en dessinant une copie sombre et floue d'un élément, décalée par rapport à l'original. Dans le design mobile, les ombres remplissent trois fonctions : montrer la hiérarchie (plus l'élément est haut, plus l'ombre est longue), signaler l'interactivité (les boutons avec ombre semblent cliquables), séparer le contenu du fond (les cartes avec ombre se lisent comme des blocs séparés).
Dans le Material Design de Google, les ombres sont inextricablement liées au concept d'elevation. Chaque élément occupe une certaine hauteur dans la coordonnée z, et l'ombre est une projection de l'éclairage supérieur. Les Human Interface Guidelines d'Apple ne prescrivent pas de système de hauteur strict, mais recommandent d'utiliser les ombres pour créer une hiérarchie visuelle et de la profondeur dans les interfaces.
Selon une étude du Nielsen Norman Group (2024), des ombres correctement placées réduisent le temps de recherche d'un élément interactif de 24%. Les ombres aident également les utilisateurs malvoyants à distinguer les limites des éléments. Cependant, l'utilisation excessive d'ombres (3+ niveaux sur un écran) augmente la charge cognitive et altère la perception.
Sous iOS, les ombres sont implémentées via les propriétés de CALayer : shadowColor définit la couleur de l'ombre, shadowOffset — le décalage en CGSize (x, y), shadowRadius — le rayon de flou, shadowOpacity — l'opacité de 0 à 1. Pour les performances, il est également recommandé de définir shadowPath — un UIBezierPath qui décrit la forme de l'ombre, afin que Core Animation ne la calcule pas automatiquement.
// UIKit : configuration d'ombre via CALayer
import UIKit
let cardView = UIView()
cardView.backgroundColor = .systemBackground
cardView.layer.cornerRadius = 12
// Configuration d'ombre
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.15
cardView.layer.shadowOffset = CGSize(width: 0, height: 4)
cardView.layer.shadowRadius = 8
"> Optimisation : shadowPath explicite
cardView.layer.shadowPath = UIBezierPath(
roundedRect: cardView.bounds,
cornerRadius: 12
).cgPath
Dans SwiftUI, les ombres sont définies via le modificateur .shadow() avec les paramètres color, radius, x, y. SwiftUI applique automatiquement shadowPath et gère les performances de rendu. Pour les ombres avancées (ombres intérieures, ombres multiples), utilisez overlay() avec LinearGradient.
// SwiftUI : ombre via le modificateur shadow
import SwiftUI
VStack {
Text("Carte avec ombre")
.padding()
.background(Color.white)
.cornerRadius(16)
.shadow(
color: .black.opacity(0.15),
radius: 10,
x: 0,
y: 4
)
}
Performances des ombres sous iOS : si shadowPath n'est pas défini, Core Animation calcule l'ombre en fonction de la forme de la couche à chaque image, ce qui peut provoquer des ralentissements pendant l'animation. Pour les éléments statiques, définissez toujours shadowPath. Pour les éléments animés, utilisez shouldRasterize = true sur CALayer pour mettre en cache l'ombre dans une couche séparée. Dans SwiftUI, la rastérisation est gérée via .drawingGroup().
Sous Android, les ombres dans Material Design sont implémentées via elevation — une propriété View qui définit la hauteur de l'élément dans la coordonnée z. Le système rend automatiquement une ombre en fonction de l'elevation et d'outlineProvider (contour de l'élément). Plus l'elevation est élevée, plus l'ombre est grande et floue. Pour les Views qui n'héritent pas de Material Design (par exemple AppCompat), les ombres ne fonctionnent qu'avec l'accélération matérielle activée.
<!-- XML : elevation via les attributs -->
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="6dp">
<!-- Contenu de la carte -->
</androidx.cardview.widget.CardView>
Pour les ombres personnalisées dans le système de vues Android, utilisez OutlineProvider. ViewOutlineProvider permet de définir une forme arbitraire via outline.setRoundRect() ou outline.setPath(). Sans OutlineProvider, l'ombre est rendue en fonction de la forme rectangulaire de la View, sans tenir compte de l'arrondi des coins.
// Kotlin : OutlineProvider personnalisé pour l'ombre
import android.graphics.Outline
import android.view.View
import android.view.ViewOutlineProvider
val myView: View = findViewById(R.id.myView)
myView.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(
0, 0, view.width, view.height,
12f // rayon de coin
)
}
}
myView.elevation = 8f // hauteur d'ombre en px
myView.translationZ = 4f // hauteur supplémentaire lors de l'appui
Dans Jetpack Compose, les ombres sont gérées via Modifier.shadow() et elevation dans les composants Card et Surface. Modifier.shadow() accepte elevation et shape. Contrairement au système de vues, Compose rend les ombres dans son propre pipeline de rendu, offrant un comportement plus prévisible sur toutes les versions d'Android.
// Jetpack Compose : ombre via Modifier.shadow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
Card(
modifier = Modifier.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(12.dp),
clip = false
),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Text("Card avec ombre personnalisée")
}
L'utilisation correcte des ombres améliore la perception de l'interface, tandis qu'une utilisation incorrecte crée un bruit visuel et dégrade les performances. Material Design définit cinq niveaux d'elevation : 0 dp (pas d'ombre), 1–4 dp (boutons, chips), 6–12 dp (cartes, menus), 16–24 dp (dialogues, feuilles inférieures), 24+ dp (fenêtres modales, FAB).
| Niveau | Elevation | Éléments | Caractéristique de l'ombre |
|---|---|---|---|
| 0 | 0 dp | Texte, icônes, fond | Pas d'ombre, couche plate |
| 1 | 1–4 dp | Boutons, champs de saisie | Ombre légère, petit décalage |
| 2 | 6–12 dp | Cartes, Snackbar | Ombre modérée, flou notable |
| 3 | 16–24 dp | Dialogues, menus | Ombre profonde, grand flou |
| 4 | 24+ dp | Fenêtres modales | Ombre maximale, élément flotte |
Bonnes pratiques : (1) n'utilisez pas d'ombres pour les éléments qui ne doivent pas attirer l'attention (fonds, séparateurs), (2) la hauteur de l'ombre doit correspondre à l'importance de l'élément — plus il est important, plus elle est haute, (3) lors de l'appui sur un bouton, diminuez l'elevation avec animation pour un retour tactile, (4) évitez les ombres sur les fonds sombres — utilisez overlay (éclaircir/foncer) au lieu d'une ombre. Dans Material You (Android 12+), les ombres s'adaptent au thème : sur fond sombre, l'elevation est remplacée par une couche de couleur (surface tint).
Dans Flutter, les ombres sont implémentées via BoxShadow à l'intérieur de BoxDecoration. BoxShadow accepte color, offset, blurRadius et spreadRadius. Flutter prend en charge plusieurs ombres : passer une liste de BoxShadow permet de créer des effets complexes — par exemple, une ombre extérieure + un surlignage intérieur. PhysicalModel est une alternative qui simule une ombre physique basée sur la forme (BeveledRectangleBorder, CircleBorder).
// Flutter : ombre via BoxDecoration + BoxShadow
import 'package:flutter/material.dart';
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.0),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
offset: Offset(0, 4),
blurRadius: 8.0,
spreadRadius: 0,
),
],
),
child: Text('Conteneur avec ombre'),
)
// PhysicalModel pour ombre physique
PhysicalModel(
color: Colors.white,
elevation: 6.0,
shadowColor: Colors.black.withOpacity(0.2),
borderRadius: BorderRadius.circular(12.0),
child: Text('Modèle physique avec ombre'),
)
Performances : BoxShadow avec plusieurs ombres peut provoquer un repeint à chaque modification. Pour les éléments statiques, utilisez PhysicalModel — il met en cache l'ombre dans une couche séparée (RepaintBoundary). Dans Flutter 3.16+, l'elevation Material 3 avec ombres adaptatives est prise en charge, ce qui change la couleur et le flou en fonction du thème (clair/sombre).
Foire aux questions
Elevation est la hauteur numérique d'un élément dans la coordonnée z, tandis que shadow (ombre) est l'effet visuel créé par cette hauteur. Sous Android, elevation est une propriété View qui génère automatiquement une ombre. En design, elevation et shadow sont souvent utilisés de manière interchangeable, mais techniquement, elevation est la cause et shadow est l'effet.
Vérifiez : (1) la View a une elevation supérieure à 0, (2) outlineProvider est défini (si la View ne provient pas de la bibliothèque Material), (3) l'accélération matérielle est activée (android:hardwareAccelerated="true" dans le manifeste). CardView gère automatiquement outlineProvider. Pour les Views personnalisées, appelez view.outlineProvider = ViewOutlineProvider.BACKGROUND.
CALayer ne prend pas en charge les ombres intérieures directement. Utilisez une combinaison : CAGradientLayer avec couleur noire et transparence à l'intérieur d'un masque, ou superposez une UIImageView avec une ombre intérieure tramée. Dans SwiftUI, l'ombre intérieure est implémentée via overlay avec LinearGradient et blendMode(.multiply).
Sous Android, utilisez translationZ pour l'animation : au repos elevation = 2 dp, lors de l'appui translationZ = 4 dp (hauteur totale 6 dp). Sous iOS, animez shadowOffset et shadowRadius simultanément avec la transformation du bouton. Dans SwiftUI, utilisez withAnimation avec le changement d'elevation via Modifier.shadow(). Dans Flutter, utilisez AnimatedContainer avec le changement de BoxShadow.
Material Design recommande de ne pas dépasser trois niveaux d'elevation sur un même écran. S'il y a plus d'éléments avec ombre, regroupez-les avec la même hauteur. Un nombre excessif d'ombres crée un bruit visuel et réduit les performances de rendu sur les appareils de milieu et d'entrée de gamme.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi