SwiftUI gradients and shadow styles

Want to add some gradients and drop shadows to your flat design? Starting with iOS 16, SwiftUI adds a gradient property to Color and some convenient modifiers to ShapeStyle to add drop and inner shadows to views.

Color Gradients

The gradient property of Color returns a color gradient of type AnyGradient which you can use as a ShapeStyle:

Text("Hello")
.padding()
.background(.yellow.gradient,
  in: RoundedRectangle(cornerRadius: 4))

Black hello text in rounded rectangle with a yellow gradient background

Or when setting the background style for views:

VStack {
  Image(systemName: "person")
  Text("Name")
}
.background(in: RoundedRectangle(cornerRadius: 2)
  .inset(by: -10))
.backgroundStyle(.blue.gradient)
.foregroundStyle(.white)

White person icon and name text in a rounded blue gradient rectangle

Shadow Styles

The ShadowStyle gives us some convenient modifiers to add drop and inner shadows to colors. The drop shadow takes a radius for the shadow’s size and optional horizontal (x) and vertical (y) offsets:

Rectangle()
.fill(.orange
      .shadow(.drop(radius: 2, x: 5, y: 5)))
.frame(width: 75, height: 50)

Orange rectangle with drop shadow offset to bottom right

The inner shadow modifier follows the same format. This time I’ve used it with a gradient:

Rectangle()
.fill(.orange
      .gradient
      .shadow(.inner(radius: 1, x: -1, y: -1)))
.frame(width: 75, height: 50)

Orange rectangle with inner shadow offset from bottom right

The shadow style works well with SF symbols. For example, applying a shadow to both the foreground and background styles:

VStack {
  Image(systemName: "person")
  Text("Name")
}
.background(in: RoundedRectangle(cornerRadius: 2).inset(by: -10))
.backgroundStyle(.blue.gradient
  .shadow(.drop(radius: 1, x: 2, y: 2)))
.foregroundStyle(.white
  .shadow(.drop(radius: 1, x: 2, y: 2)))

White person icon and name text in a rounded blue gradient rectangle with drop shadow

Animating Changes

The gradient and shadow effects are animatable when changed. Let’s build an “On Air” indicator that switches between off/on states:

Side by side images with a microphone and on-air text. Left image has gray gradient and dimmed text. Right image has red gradient and black text with drop shadow

I built the view with an HStack in a rounded rectangle:

struct OnAirView: View {
  @Binding var onAir: Bool

  var body: some View {
    HStack {
      Image(systemName: "mic")
      Text("On Air")
    }
    .background(in: RoundedRectangle(cornerRadius: 2)
      .inset(by: -10))
    .backgroundStyle(onAir ? onBackground : offBackground)
    .foregroundStyle(onAir ? onForeground : offForeground)
  }
}

I switch the background and foreground styles depending on the onAir state. I find it easiest to define those styles as separate properties. First the background styles:

private let offBackground: AnyShapeStyle = AnyShapeStyle(
  .gray.gradient
  .shadow(.drop(radius: 0)))

private let onBackground: AnyShapeStyle = AnyShapeStyle(
  .red.gradient
  .shadow(.drop(radius: 2, x: 2, y: 2)))

This switches between a gray gradient with no drop shadow (radius 0) and a red gradient with a drop shadow. It’s better to remove the shadow by applying a zero radius rather than omit it to avoid a harsh animation.

The foreground styles used for the text and symbol follow a similar pattern:

private let offForeground: AnyShapeStyle = AnyShapeStyle(
  .black.opacity(0.4)
  .shadow(.drop(radius: 0)))

private let onForeground: AnyShapeStyle = AnyShapeStyle(
  .black.opacity(1)
  .shadow(.drop(radius: 2, x: 2, y: 2)))

Learn More