June 8, 2019
Update (6/13/2019): The original version of this post featured the wrong colors in the ColorCompatibility enum at the end of the post. This has now been fixed.
Update (9/21/2019): The original version of this post included systemBrown, a color which was removed in the Xcode 11 GM. It also contained unnecessary availability checks in the code at the bottom (system colors like red, blue etc have been available since iOS 7). The code has been updated, and the tables in this post still list all system and element colors - even those which have been available for a while, since they're different in dark mode vs light mode. I also updated a reference to Mac Catalyst.
Update (10/5/2019): A previous version of this post included the wrong default colors in the
ColorCompatibility
code at the bottom of the post. These have been updated - the defaults are now the light mode colors which are being used in production in Trestle and CIFilter.io.
Update (10/5/2019): In order to better support bug reporting on ColorCompatibility, I've released it as a library. The code now lives in this GitHub repo. You can read more about it here.
Update (10/17/2019): A previous version of this post included the wrong color values in one of the example code blocks. This has now been fixed, but I'd recommend looking at the ColorCompatibility library if you're interested in using it in your own projects.
At WWDC 2019, Apple announced that Dark Mode would be supported on iOS 13. There are some significant changes to UIKit in order to support this - many of them are detailed in the talk Implementing Dark Mode on iOS which I'd highly recommend watching.
One of the changes that makes adopting Dark Mode so easy is the new system colors API from UIColor
. On iOS 12 and older, you might have a label you want to make black, and it would work just fine - in fact, black was the default color for UILabel
s.
label.textColor = UIColor.black
But in Dark Mode, the background will also be black, which means the text won't be visible. In iOS 13+, it's better to use the new system color which will respect the user's color scheme preference:
label.textColor = UIColor.label
label
is only one example: there are 24 new color scheme agnostic UIColor
s available in iOS 13+.
Colors are organized into two groups:
label
)systemIndigo
, systemGray3
, etc).iOS 13 has 23 new element colors (label
etc) and one new standard color (systemIndigo
). However, even system colors that have been around for a while (like systemRed
) have become dynamic in iOS 13 - they might actually be different colors in dark mode vs light mode.
(Skip to the bottom if you'd like to see a list of the new colors.)
These new colors are all well and good, but most of us with existing apps will still be supporting devices with iOS 12 or lower, at least for a while. This means we'll probably be doing a lot of things like this, using Swift's #available syntax:
if #available(iOS 13, *) {
label.textColor = .label
} else {
label.textColor = .black
}
It's a workable solution, but it necessitates changing a lot of code - an if
statement for every custom label or background color! For CIFilter.io, I wondered if there was a better way. What if, instead of the if #available
, there was a way to abstract the color choice down one level, so we could do something like this?
label.textColor = ColorCompatibility.label
I wrote a small app (with SwiftUI, no less!) which displays all the system colors in the current color scheme.
This app collects all the UIColor
objects - once we have those, we can use their red/green/blue/alpha components to generate the implementation of ColorCompatibility
that we want:
enum ColorCompatibility {
static var label: UIColor {
if #available(iOS 13, *) {
return .label
}
return UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
}
static var secondaryLabel: UIColor {
if #available(iOS 13, *) {
return .secondaryLabel
}
return UIColor(red: 0.23529411764705882, green: 0.23529411764705882, blue: 0.2627450980392157, alpha: 0.6)
}
// ... 21 more definitions: full code in the link at the bottom
}
We can then use ColorCompatibility
it to set any colors we need.
Note: we can't use
@available
for these checks, since it doesn't provide a way to check if the current environment is less than a specific app version.
This approach has some great advantages:
var
, we never pre-store colors, which means that when the user switches color scheme, our app will automatically adapt as the trait collection changes 👍ColorCompatibility
with UIColor
🎉Hopefully this makes your app's transition to dark mode easier!
If you're interested in dark mode and new UIColor apis, you can follow me on Twitter as I continue to play around with these new iOS technologies.
For those interested in iOS 13+ system colors but not wanting (or not able) to compile the sample app, here's a list of the system colors in light and dark mode, with their hex codes and RGBA values:
Name | Color | Hex String | RGBA |
label | #000000ff | rgba(0.0, 0.0, 0.0, 1.0) | |
secondaryLabel | #3c3c4399 | rgba(60.0, 60.0, 67.0, 0.6) | |
tertiaryLabel | #3c3c434c | rgba(60.0, 60.0, 67.0, 0.3) | |
quaternaryLabel | #3c3c432d | rgba(60.0, 60.0, 67.0, 0.18) | |
systemFill | #78788033 | rgba(120.0, 120.0, 128.0, 0.2) | |
secondarySystemFill | #78788028 | rgba(120.0, 120.0, 128.0, 0.16) | |
tertiarySystemFill | #7676801e | rgba(118.0, 118.0, 128.0, 0.12) | |
quaternarySystemFill | #74748014 | rgba(116.0, 116.0, 128.0, 0.08) | |
placeholderText | #3c3c434c | rgba(60.0, 60.0, 67.0, 0.3) | |
systemBackground | #ffffffff | rgba(255.0, 255.0, 255.0, 1.0) | |
secondarySystemBackground | #f2f2f7ff | rgba(242.0, 242.0, 247.0, 1.0) | |
tertiarySystemBackground | #ffffffff | rgba(255.0, 255.0, 255.0, 1.0) | |
systemGroupedBackground | #f2f2f7ff | rgba(242.0, 242.0, 247.0, 1.0) | |
secondarySystemGroupedBackground | #ffffffff | rgba(255.0, 255.0, 255.0, 1.0) | |
tertiarySystemGroupedBackground | #f2f2f7ff | rgba(242.0, 242.0, 247.0, 1.0) | |
separator | #3c3c4349 | rgba(60.0, 60.0, 67.0, 0.29) | |
opaqueSeparator | #c6c6c8ff | rgba(198.0, 198.0, 200.0, 1.0) | |
link | #007affff | rgba(0.0, 122.0, 255.0, 1.0) | |
darkText | #000000ff | rgba(0.0, 0.0, 0.0, 1.0) | |
lightText | #ffffff99 | rgba(255.0, 255.0, 255.0, 0.6) | |
systemBlue | #007affff | rgba(0.0, 122.0, 255.0, 1.0) | |
systemGreen | #34c759ff | rgba(52.0, 199.0, 89.0, 1.0) | |
systemIndigo | #5856d6ff | rgba(88.0, 86.0, 214.0, 1.0) | |
systemOrange | #ff9500ff | rgba(255.0, 149.0, 0.0, 1.0) | |
systemPink | #ff2d55ff | rgba(255.0, 45.0, 85.0, 1.0) | |
systemPurple | #af52deff | rgba(175.0, 82.0, 222.0, 1.0) | |
systemRed | #ff3b30ff | rgba(255.0, 59.0, 48.0, 1.0) | |
systemTeal | #5ac8faff | rgba(90.0, 200.0, 250.0, 1.0) | |
systemYellow | #ffcc00ff | rgba(255.0, 204.0, 0.0, 1.0) | |
systemGray | #8e8e93ff | rgba(142.0, 142.0, 147.0, 1.0) | |
systemGray2 | #aeaeb2ff | rgba(174.0, 174.0, 178.0, 1.0) | |
systemGray3 | #c7c7ccff | rgba(199.0, 199.0, 204.0, 1.0) | |
systemGray4 | #d1d1d6ff | rgba(209.0, 209.0, 214.0, 1.0) | |
systemGray5 | #e5e5eaff | rgba(229.0, 229.0, 234.0, 1.0) | |
systemGray6 | #f2f2f7ff | rgba(242.0, 242.0, 247.0, 1.0) |
Name | Color | Hex String | RGBA |
label | #ffffffff | rgba(255.0, 255.0, 255.0, 1.0) | |
secondaryLabel | #ebebf599 | rgba(235.0, 235.0, 245.0, 0.6) | |
tertiaryLabel | #ebebf54c | rgba(235.0, 235.0, 245.0, 0.3) | |
quaternaryLabel | #ebebf52d | rgba(235.0, 235.0, 245.0, 0.18) | |
systemFill | #7878805b | rgba(120.0, 120.0, 128.0, 0.36) | |
secondarySystemFill | #78788051 | rgba(120.0, 120.0, 128.0, 0.32) | |
tertiarySystemFill | #7676803d | rgba(118.0, 118.0, 128.0, 0.24) | |
quaternarySystemFill | #7676802d | rgba(118.0, 118.0, 128.0, 0.18) | |
placeholderText | #ebebf54c | rgba(235.0, 235.0, 245.0, 0.3) | |
systemBackground | #000000ff | rgba(0.0, 0.0, 0.0, 1.0) | |
secondarySystemBackground | #1c1c1eff | rgba(28.0, 28.0, 30.0, 1.0) | |
tertiarySystemBackground | #2c2c2eff | rgba(44.0, 44.0, 46.0, 1.0) | |
systemGroupedBackground | #000000ff | rgba(0.0, 0.0, 0.0, 1.0) | |
secondarySystemGroupedBackground | #1c1c1eff | rgba(28.0, 28.0, 30.0, 1.0) | |
tertiarySystemGroupedBackground | #2c2c2eff | rgba(44.0, 44.0, 46.0, 1.0) | |
separator | #54545899 | rgba(84.0, 84.0, 88.0, 0.6) | |
opaqueSeparator | #38383aff | rgba(56.0, 56.0, 58.0, 1.0) | |
link | #0984ffff | rgba(9.0, 132.0, 255.0, 1.0) | |
darkText | #000000ff | rgba(0.0, 0.0, 0.0, 1.0) | |
lightText | #ffffff99 | rgba(255.0, 255.0, 255.0, 0.6) | |
systemBlue | #0a84ffff | rgba(10.0, 132.0, 255.0, 1.0) | |
systemGreen | #30d158ff | rgba(48.0, 209.0, 88.0, 1.0) | |
systemIndigo | #5e5ce6ff | rgba(94.0, 92.0, 230.0, 1.0) | |
systemOrange | #ff9f0aff | rgba(255.0, 159.0, 10.0, 1.0) | |
systemPink | #ff375fff | rgba(255.0, 55.0, 95.0, 1.0) | |
systemPurple | #bf5af2ff | rgba(191.0, 90.0, 242.0, 1.0) | |
systemRed | #ff453aff | rgba(255.0, 69.0, 58.0, 1.0) | |
systemTeal | #64d2ffff | rgba(100.0, 210.0, 255.0, 1.0) | |
systemYellow | #ffd60aff | rgba(255.0, 214.0, 10.0, 1.0) | |
systemGray | #8e8e93ff | rgba(142.0, 142.0, 147.0, 1.0) | |
systemGray2 | #636366ff | rgba(99.0, 99.0, 102.0, 1.0) | |
systemGray3 | #48484aff | rgba(72.0, 72.0, 74.0, 1.0) | |
systemGray4 | #3a3a3cff | rgba(58.0, 58.0, 60.0, 1.0) | |
systemGray5 | #2c2c2eff | rgba(44.0, 44.0, 46.0, 1.0) | |
systemGray6 | #1c1c1eff | rgba(28.0, 28.0, 30.0, 1.0) |
The full, generated implementation of ColorCompatibility
, which is used by Trestle and CIFilter.io, is available here.
I'm Noah, a software developer based in the San Francisco Bay Area. I focus mainly on full stack web and iOS development