Shadow (ombra) — un effetto visivo nelle interfacce mobili che crea l'illusione di profondità e gerarchia tra gli elementi. Le ombre mostrano quale elemento si trova più in alto nel piano z e, quindi, è più importante o interattivo. Secondo le Material Design Guidelines (Google, 2026), le ombre di sistema utilizzano cinque altezze chiave (elevation): 0 dp (piatto), 1–4 dp (pulsanti), 6–12 dp (carte), 16–24 dp (dialoghi), 24+ dp (finestre modali). Maggiori informazioni sull'altezza degli elementi nell'articolo su Elevation.
Punti principali
L'ombra (shadow) nelle interfacce utente è un effetto visivo che simula illuminazione e profondità. Un'ombra viene creata disegnando una copia scura e sfocata di un elemento, spostata rispetto all'originale. Nel design mobile, le ombre hanno tre funzioni: mostrare la gerarchia (più alto è l'elemento, più lunga è l'ombra), segnalare l'interattività (i pulsanti con ombra sembrano cliccabili), separare il contenuto dallo sfondo (le carte con ombra si leggono come blocchi separati).
Nel Material Design di Google, le ombre sono indissolubilmente legate al concetto di elevation. Ogni elemento occupa una certa altezza nella coordinata z e l'ombra è una proiezione dell'illuminazione dall'alto. Le Human Interface Guidelines di Apple non prescrivono un sistema di altezze rigoroso, ma raccomandano di utilizzare le ombre per creare gerarchia visiva e profondità nelle interfacce.
Secondo uno studio del Nielsen Norman Group (2024), le ombre posizionate correttamente riducono il tempo per trovare un elemento interattivo del 24%. Le ombre aiutano anche gli utenti con disabilità visive a distinguere i confini degli elementi. Tuttavia, l'uso eccessivo di ombre (3+ livelli su uno schermo) aumenta il carico cognitivo e compromette la percezione.
Su iOS, le ombre vengono implementate tramite le proprietà di CALayer: shadowColor imposta il colore dell'ombra, shadowOffset — l'offset in CGSize (x, y), shadowRadius — il raggio di sfocatura, shadowOpacity — l'opacità da 0 a 1. Per le prestazioni, si consiglia anche di impostare shadowPath — un UIBezierPath che descrive la forma dell'ombra, in modo che Core Animation non la calcoli automaticamente.
// UIKit: configurazione dell'ombra tramite CALayer
import UIKit
let cardView = UIView()
cardView.backgroundColor = .systemBackground
cardView.layer.cornerRadius = 12
// Configurazione ombra
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.15
cardView.layer.shadowOffset = CGSize(width: 0, height: 4)
cardView.layer.shadowRadius = 8
"> Ottimizzazione: shadowPath esplicito
cardView.layer.shadowPath = UIBezierPath(
roundedRect: cardView.bounds,
cornerRadius: 12
).cgPath
In SwiftUI, le ombre vengono impostate tramite il modificatore .shadow() con parametri color, radius, x, y. SwiftUI applica automaticamente shadowPath e gestisce le prestazioni di rendering. Per ombre avanzate (ombre interne, ombre multiple), utilizzare overlay() con LinearGradient.
// SwiftUI: ombra tramite modificatore shadow
import SwiftUI
VStack {
Text("Carta con ombra")
.padding()
.background(Color.white)
.cornerRadius(16)
.shadow(
color: .black.opacity(0.15),
radius: 10,
x: 0,
y: 4
)
}
Prestazioni delle ombre su iOS: se shadowPath non è impostato, Core Animation calcola l'ombra in base alla forma del layer ogni fotogramma, il che può causare rallentamenti durante l'animazione. Per elementi statici, impostare sempre shadowPath. Per elementi animati, utilizzare shouldRasterize = true su CALayer per memorizzare nella cache l'ombra in un layer separato. In SwiftUI, la rasterizzazione viene gestita tramite .drawingGroup().
In Android, le ombre in Material Design vengono implementate tramite elevation — una proprietà View che imposta l'altezza dell'elemento nella coordinata z. Il sistema renderizza automaticamente un'ombra in base all'elevation e a outlineProvider (contorno dell'elemento). Più alta è l'elevation, più grande e sfocata è l'ombra. Per le View che non ereditano da Material Design (es. AppCompat), le ombre funzionano solo con l'accelerazione hardware abilitata.
<!-- XML: elevation tramite attributi -->
<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">
<!-- Contenuto della carta -->
</androidx.cardview.widget.CardView>
Per ombre personalizzate nel sistema di View Android, utilizzare OutlineProvider. ViewOutlineProvider consente di impostare una forma arbitraria tramite outline.setRoundRect() o outline.setPath(). Senza OutlineProvider, l'ombra viene renderizzata in base alla forma rettangolare della View, senza considerare l'arrotondamento degli angoli.
// Kotlin: OutlineProvider personalizzato per ombra
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 // raggio angolo
)
}
}
myView.elevation = 8f // altezza ombra in px
myView.translationZ = 4f // altezza aggiuntiva alla pressione
In Jetpack Compose, le ombre vengono gestite tramite Modifier.shadow() e elevation nei componenti Card e Surface. Modifier.shadow() accetta elevation e shape. A differenza del sistema di View, Compose renderizza le ombre nella propria pipeline di rendering, offrendo un comportamento più prevedibile su tutte le versioni di Android.
// Jetpack Compose: ombra tramite 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 con ombra personalizzata")
}
L'uso corretto delle ombre migliora la percezione dell'interfaccia, mentre l'uso improprio crea rumore visivo e degrada le prestazioni. Material Design definisce cinque livelli di elevation: 0 dp (nessuna ombra), 1–4 dp (pulsanti, chip), 6–12 dp (carte, menu), 16–24 dp (dialoghi, fogli inferiori), 24+ dp (finestre modali, FAB).
| Livello | Elevation | Elementi | Caratteristica dell'ombra |
|---|---|---|---|
| 0 | 0 dp | Testo, icone, sfondo | Nessuna ombra, strato piatto |
| 1 | 1–4 dp | Pulsanti, campi di input | Ombra leggera, piccolo offset |
| 2 | 6–12 dp | Carte, Snackbar | Ombra moderata, sfocatura notevole |
| 3 | 16–24 dp | Dialoghi, menu | Ombra profonda, grande sfocatura |
| 4 | 24+ dp | Finestre modali | Ombra massima, elemento fluttua |
Buone pratiche: (1) non utilizzare ombre per elementi che non devono attirare l'attenzione (sfondi, divisori), (2) l'altezza dell'ombra deve corrispondere all'importanza dell'elemento — più importante, più alta, (3) quando si preme un pulsante, ridurre l'elevation con animazione per il feedback tattile, (4) evitare ombre su sfondi scuri — utilizzare overlay (schiarire/scurire) invece dell'ombra. In Material You (Android 12+), le ombre si adattano al tema: su sfondi scuri, l'elevation viene sostituita da uno strato di colore (surface tint).
In Flutter, le ombre vengono implementate tramite BoxShadow all'interno di BoxDecoration. BoxShadow accetta color, offset, blurRadius e spreadRadius. Flutter supporta più ombre: passare un elenco di BoxShadow consente di creare effetti complessi — ad esempio, un'ombra esterna + un'evidenziazione interna. PhysicalModel è un'alternativa che simula un'ombra fisica basata sulla forma (BeveledRectangleBorder, CircleBorder).
// Flutter: ombra tramite 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('Contenitore con ombra'),
)
// PhysicalModel per ombra fisica
PhysicalModel(
color: Colors.white,
elevation: 6.0,
shadowColor: Colors.black.withOpacity(0.2),
borderRadius: BorderRadius.circular(12.0),
child: Text('Modello fisico con ombra'),
)
Prestazioni: BoxShadow con più ombre può causare un nuovo rendering a ogni modifica. Per elementi statici, utilizzare PhysicalModel — memorizza nella cache l'ombra in un layer separato (RepaintBoundary). In Flutter 3.16+, è supportata elevation di Material 3 con ombre adattive, che cambiano colore e sfocatura in base al tema (chiaro/scuro).
Domande frequenti
Elevation è l'altezza numerica di un elemento nella coordinata z, mentre shadow (ombra) è l'effetto visivo creato da questa altezza. In Android, elevation è una proprietà View che genera automaticamente un'ombra. Nel design, elevation e shadow sono spesso usati in modo intercambiabile, ma tecnicamente elevation è la causa e shadow è l'effetto.
Verificare: (1) la View ha elevation maggiore di 0, (2) outlineProvider è impostato (se la View non proviene dalla libreria Material), (3) l'accelerazione hardware è abilitata (android:hardwareAccelerated="true" nel manifest). CardView gestisce automaticamente outlineProvider. Per View personalizzate, chiamare view.outlineProvider = ViewOutlineProvider.BACKGROUND.
CALayer non supporta le ombre interne direttamente. Utilizzare una combinazione: CAGradientLayer con colore nero e trasparenza all'interno di una maschera, o sovrapporre una UIImageView con un'ombra interna rasterizzata. In SwiftUI, l'ombra interna viene implementata tramite overlay con LinearGradient e blendMode(.multiply).
In Android, utilizzare translationZ per l'animazione: a riposo elevation = 2 dp, alla pressione translationZ = 4 dp (altezza totale 6 dp). Su iOS, animare shadowOffset e shadowRadius contemporaneamente alla trasformazione del pulsante. In SwiftUI, utilizzare withAnimation con il cambiamento di elevation tramite Modifier.shadow(). In Flutter, utilizzare AnimatedContainer con il cambiamento di BoxShadow.
Material Design raccomanda non più di tre livelli di elevation su uno schermo. Se ci sono più elementi con ombra, raggrupparli con la stessa altezza. Un numero eccessivo di ombre crea rumore visivo e riduce le prestazioni di rendering sui dispositivi di fascia media e bassa.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche