Working with colors is one of the most common tasks for any developer in SwiftUI. However, we almost always stick to the basics; we apply the necessary colors and sometimes we also change their opacity. Occasionally we need to go deeper and use a combination of view modifiers or other techniques to manage a colorful visual outcome, ignoring that Color views have built-in capabilities that can often lead to similar results. So, in this post we'll see some not so well-known Color features; levels, gradients, mixing and shadows.
Presenting color of various levels
By default, the primary representation of a Color
is what we use in SwiftUI. However, it's often necessary, to use variations of it, and before we resort to other techniques that alter the original color, there are built-in alternatives to consider first.
In fact, each Color
object in SwiftUI has the following grades:
- secondary
- tertiary
- quaternary
We access them by chaining to the original color. The following example fills a Rectangle shape and demonstrates that:
1 2 3 4 5 6 |
Rectangle().fill(.indigo) Rectangle().fill(.indigo.secondary) Rectangle().fill(.indigo.tertiary) Rectangle().fill(.indigo.quaternary) |

See that the additional levels become softer the deeper we get in light mode. The exact opposite happens in the dark mode though:

In Xcode, you'll also find the quinary
level in addition to all the above, but it seems to have no difference to quaternary
.
These levels make it extremely easy to get various grades of a color, but note something really important:
These are not Color
objects but ShapeStyle
values, and what they actually do is to affect how the color is presented. They may appear visually similar, but besides that, we cannot treat them like real colors; for instance, we cannot extract their Hex or RGB values.
Still, if the purpose is to just show or apply shades of a color, the above should be the first thing to try out.
Showing standard gradients
When it comes to gradients, types like the LinearGradient
or RadialGradient
are the first —and usually the only— candidates that come in mind in order to achieve the desired result. However, when the goal is to show a gradient based on shades of the same color, then there's a simpler way to do so. That is, to chain the gradient
property to the color itself.
For instance:
1 2 3 4 |
Rectangle() .fill(.mint.gradient) |

The gradient
property returns an AnyGradient
instance, which can be used anywhere a ShapeStyle
is accepted (such as in fill
, background
, or foregroundStyle
). But just as before, we can apply the secondary
and the other style levels here too, and achieve gradients of various grades:
1 2 3 4 5 6 |
Rectangle().fill(.mint.gradient) Rectangle().fill(.mint.gradient.secondary) Rectangle().fill(.mint.gradient.tertiary) Rectangle().fill(.mint.gradient.quaternary) |

Mixing colors
As of iOS 18, it's more than straightforward to mix colors thanks to the mix(with:by:)
instance method of the Color
class. We can access this method through any color instance, and it returns a new color object composed by the mix of the first two.
The following code demonstrates how to mix the pink and yellow colors. The 0.65
value means that the second color —the with
argument— contributes 65% to the new color, while the current one (pink
in this case) contributes 35%. This combination results in a warm orange:
1 2 3 4 5 6 |
Rectangle() .fill( .pink.mix(with: .yellow, by: 0.65) ) |

If necessary, we can chain multiple mix(with:by:)
methods as shown next:
1 2 3 4 5 6 7 8 9 |
Rectangle() .fill( .pink .mix(with: .yellow, by: 0.4) .mix(with: .green, by: 0.35) .mix(with: .blue, by: 0.5) ) |

The above:
- Blends 40% yellow with 60% pink.
- Then blends 35% green with the first result.
- Finally blends 50% blue with the second result.
Note that it's not mandatory to set hardcoded values as arguments to the by
parameter. It's perfectly fine to use a property that changes dynamically inside the app.
Dropping shadows
Color values make it easy to apply a shadow without using the shadow
modifier. There is the shadow(_:)
instance method for that, which allows us to create either a drop or an inner shadow.
The arguments in both kinds of shadow are the same as those in the shadow
modifier. Specifically, the only mandatory argument to provide is the radius
value. Optionally, we can set a color, and offset values for either the horizontal or vertical axis, or both.
The following example demonstrates how to create a drop shadow, specifying the color, radius and vertical offset:
1 2 3 4 5 |
Rectangle() .fill(.teal.shadow(.drop(color: .gray, radius: 5, y: 5))) .frame(height: 200) |

On the other hand, the following code creates an inner shadow:
1 2 3 4 5 |
Rectangle() .fill(.teal.shadow(.inner(color: .gray, radius: 5, x: 0, y: -20))) .frame(height: 200) |

Conclusion
As you see, there are color APIs that can speed up certain tasks that could be also done otherwise but with some more effort, so it'd be good to keep them in our tool belt for use whenever necessary. Displaying various levels of colors and basic gradients is a no-brainer, while color mixing is a great new feature as of iOS 18. And if you like depth, then applying shadow through color values is as easy as it gets. I hope you found this post useful. Thank you for reading.