We recently undertook updating an older Xamarin.Forms app to include Dark Mode support on iOS. Newer versions of Xamarin.Forms makes it pretty easy to set up a good framework. There are some useful blog posts here and here on how to set up a framework in your app, and some very useful Microsoft documentation here.
But then we started to run into bugs, one by one. Bits of the UI just weren’t responding as we expected to Dark Mode. Thus started a journey of discovery to methodically understand how background and text colors work. Hopefully you will find this helpful in your Dark Mode efforts as well.
We’re only looking in detail at iOS, which has made Dark Mode opt-out. We’re also focusing on issues that have to do with text. There are other important issues, like images, but that’s for another post.
What does Dark Mode do?
From a high level, switching between light and dark mode simply causes some system managed colors to change. In Light Mode, the default system background is white and text is black, and vice versa for Dark Mode (not necessarily pure white and black, but close). But this is at the iOS level. Xamarin.Forms sits between your app and iOS, and Xamarin.Forms sets some colors on its own. This means that a Xamarin.Forms app will see default behavior that doesn’t exactly match the iOS platform.
Brief background on BackgroundColor
Before getting into details, here’s a little context about BackgroundColor
and how it usually works. This shouldn’t be a surprise to Xamarin.Forms developers.
Everything that takes up space on the screen has a BackgroundColor
. For most controls and containers, it’s transparent, letting its parent’s color through. Consider:
<StackLayout BackgroundColor="HotPink" x:Name="S1"> <StackLayout x:Name="S2"> <Grid> <Label Text="Hello there"/> ...
The text will appear with a BackgroundColor
of HotPink because the StackLayout S2 and Grid default to transparent BackgroundColor
, letting the HotPink from S1 shine through. This “nesting” principle is pretty intuitive, and generally works pretty well.
Where things get interesting
Common sources of Dark Mode app bugs are where there are Xamarin.Forms defaults that aren’t compatible with iOS when in Dark Mode, and times when Xamarin.Forms doesn’t obey the “nesting” principle for BackgroundColor
.
An important Xamarin.Forms default is Page, where BackgroundColor
defaults to White, regardless of Light vs Dark Mode. There are others, which we’ll get to later.
There is no Xamarin.Forms default for TextColor
on Label
, so it uses the iOS defined color. The iOS color changes between themes. For example, let’s look at an example from a page that has some unexpected Dark Mode behavior:
<CollectionView x:Name="ItemsListView" ItemsSource="{Binding Items}">
<CollectionView.Header>
<SearchBar Grid.Row="1"></SearchBar>
</CollectionView.Header>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:Item">
<Label Text="{Binding Text}" FontSize="16" />
<Label Text="{Binding Description}" FontSize="13" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Now compare how they look in Light versus Dark Mode:
Because Page is White by default, and CollectionView
doesn’t override it, the whole page remains White in both. Label.TextColor
in the collection’s ItemTemplate
changes based on the Theme, as does the BackgroundColor
of the SearchBar
. As you can see, this makes the list unreadable, and the search bar has a distractingly mismatched background.
If your app design uses white backgrounds and black text, you might be relying on some of these defaults without realizing it — that’s what happened to us.
BackgroundColor
As already noted, Page
defaults to White. However Frame
, ListView
, and ListView.GroupHeaderTemplate
do not display their parent’s background color. So if you want a Frame to have the same background color as its parent, you have to explicitly set it. The default BackgroundColor
for these is typically the iOS default background, but factors like Material design can change that.
Then there are the controls that contain text. Many of these have a transparent default BackgroundColor
, but some do not. As a rule of thumb, most input controls do not have a transparent BackgroundColor
, typically they use the iOS default background color. This includes Entry
, Editor
, SearchBar
, and the Picker controls. This explains the SearchBar
example above — it’s embedded inside the ListView
, but its BackgroundColor
is the system default, which responds to theme changes.
TextColor
Luckily, understanding the default color for text is a lot easier — it almost always defaults to the system-defined color, and responds to changes in system theme.
However there are two that don’t quite work that way: TextCell
, and Span
inside Label.FormattedText
. When these are first rendered on the screen, they will pick up the system default colors, just as you’d expect. However depending on the structure of the page, they might or might not change color if the system theme changes. So even everything is fine when the app first launches, it could look wrong when the theme changes.
Setting color where it’s needed
In general, you need to style elements so they respond to theme changes when they wouldn’t otherwise (like Page
), or so they respond in a way that matches your desired colors rather than iOS defaults. You can do this with colors or styles on the elements directly, or through implicit styles.
Most containers/controls display a their parent’s background color by default, and have text color that changes automatically with the theme. This means that a lot of colors do not have to be specified because the right thing just happens.
However these are exceptions which need special attention in applying styles (for background and/or text colors):
- BackgroundColor
- Page
- Frame
- ListView
- ListView.GoupHeaderTemplate
- Entry
- Editor
- SearchBar
- Pickers
- TextColor
- TextCell (careful: implicit styles are unreliable)
- Label.FormattedText
Using XamRight to help
In order to thoroughly check our own app, in XamRight 2.50 we introduced a preview of checking for potential Dark Mode issues. This warns you about everywhere that there might be a TextColor/BackgroundColor combination where there is a mismatch between how the two colors respond (or don’t respond) to theme changes. We tested it out on the open source Xappy app, and found Dark Mode readability problems lurking in some out-of-the way UI:
Xappy Light Mode
Xappy Dark Mode
Most of the page looks great, but the bottom toolbar doesn’t look quite right. XamRight was able to point this out on this page:
If you’re adjusting to Dark Mode, try out XamRight — hopefully it’ll make it easier for you to find places in your app that still need work.