Skip to content

Instantly share code, notes, and snippets.

@filipef101
Last active November 20, 2023 21:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save filipef101/120d560a9a47ea76284cc223b01ffd97 to your computer and use it in GitHub Desktop.
Save filipef101/120d560a9a47ea76284cc223b01ffd97 to your computer and use it in GitHub Desktop.
Platform Colors and dynamic colors, easy dark mode theming in react native

Platform Colors and Dynamic Colors, easy dark mode theming in react native

We're gonna see how we can implement dark-light theming on any react native app, looking at an approach that can be very simple, and non intrusive.

For those familiar with appearance based theming, you likely seen/used implementations that rely on hooks, context etc. (useColorScheme etc)

The goal of this article is to show how we can do the same, differently, an unique approach levaraging the core PlatformColor and DynamicColorIOS API's!

Let's start with DynamicColorIOS

DynamicColorIOS({
light: color,
dark: color,
highContrastLight: color, // (optional) will fallback to "light" if not provided
highContrastDark: color, // (optional) will fallback to "dark" if not provided
});

With DynamicColorIOS we can specify colors to be used in each appearance. The object returned from DynamicColorIOS() is a drop-in replacement for a color string. With the caveat that it only works on iOS.

Instead of:

StyleSheet.create({
 rootContainer: {
  backgroundColor: 'white',
 }
});

You can do this:

const dynBackgroundColor = DynamicColorIOS({
 light: 'white',
 dark: 'black',
})
StyleSheet.create({
 rootContainer: {
  backgroundColor: dynBackgroundColor,
 }
});

Of course, ideally you should organize your colors, and theming in utility files

const getDynColor = (dark, light) => {
 if (Platform.OS === 'ios') return DynamicColorIOS({
  light,
  dark
})
return light
}
export const colors = {
  primaryBackground: getDynColor('#000', '#fff'),
  secondaryBackground: getDynColor('#D5DBE1', '#1c1c1c'),
  primaryText: getDynColor('#5E6772','#D5DBE1'),
}

And that's it, you can now use these colors in your app, and they will adapt to the appearance.

The colors of your UI will adapt in real time, without any extra re-renders, fully natively to the appearance. 🪄💫

Sadly it's iOS only, if needed to,something similar for android can be achieved with a bit more work.

You can use the PlatformColor function to access native colors on the target platform by supplying the native color’s correspondyng string value.

You can read further the docs to learn more about it, for now let's focus on android and see how to add native colors to it.

You can define colors in your colors.xml file, located in android/app/src/main/res/values/colors.xml Like so:

<resources>
  <color name="background">#ffffff</color>
  <color name="primaryText">#D5DBE1</color>
  
</resources>

And, the magic now, you can create a colors.xml (night) file, located in android/app/src/main/res/values-night/colors.xml

<resources>
  <color name="background">#000000</color>
    <color name="primaryText">#5E6772</color>
</resources>

So, in our implementation above, we can change it

const colors = {
 primaryBackground: {
  dark: '#000',
  light: '#fff',
 }
 secondaryBackground: {
  dark: '#1c1c1c',
  light: '#D5DBE1',
 }
}
const getColor = (name) => {
 Platform.select({
  ios: DynamicColorIOS({
   light:colors[name].light,
   dark:colors[name].dark,
  }),
  android: PlatformColor(`@android:color/${name}`),
  default: colors[name].light,
 })

Now we get dinamic apperance on both iOS and Android!

The downside is that we need to declare colors in two places. To address this we can create a json file, and consume it in react-native land and android land. Let me know if you want to see how to do that, and I can write another article.

This will also result in real time color updates, without any extra re-renders, fully natively, when the appearance is changed by the user! 🪄💫

Dynamic theme on iOS? Check. Dynamic theme on android? Check.

What if we don't want to even need to declare colors...

Fully adaptative appearance with PlatformColor system colors!

PlatformColor, is an utility that also allow us to hook into the native system colors.

These platform colors, will adapt to the appearance, dark or light, and also to the user preferred accent color.

If you want to build an app, or library, where you try to match the system colors, or don't want to think about colors, you can use the system colors in your app.

Some of system colors are also dynamic which will adapt to the appearance changes in real time!

You can find here some default platform color names. Or check the platform documentation:

Lets focus on a couple, and see how an util to use them in a cross platform approach could look like.

const dynamicPlatformColors = {
 background: {
  ios: 'systemBackground',
  android: '?attr/colorBackground',
 },
 secondaryBackground: {
  ios: 'secondarySystemBackground',
  android: '?attr/colorBackgroundFloating',
 },
 primaryText: {
  ios: 'label',
  android: '?attr/textColorPrimary', 
  //Note: Android has textColor and textColorPrimary, I chose textColorPrimary, but you may want textColor.  https://stackoverflow.com/a/39070850
 },
 secondaryText: {
  ios: 'secondaryLabel',
  android: '?attr/textColorSecondary',
 },
}
const platformColors = {
 ...dynamicPlatformColors,
 blue: {
  ios: 'systemBlue',
  android: '@android:color/blue',
  default: 'blue'
 },
}

const getPlatformColor(name) => {
 const color = platformColors[name]
 if (!color) return null;
 return Platform.select({
  ios: PlatformColor(color.ios),
  android: PlatformColor(color.android),
  default: color.default,
 }):
}

This is a very simple example, but you can see how we can use the platform colors, and easily adapt them to our needs.

If we take this approach and use the platform colors in our app, they will adapt to the appearance changes, and we won't need to handle hooks or context, choosing colors, etc.

Resulting in a very easy way to implement dark-light theming letting the system do the work for us.

Conclusion

If we want to least effort possible to add dark light theming you your app, just use platform colors.

If you want your own custom colors, combine DynamicColorIOS, and PlatformColor for android. Easily making an util to wrapp them.

This can be very useful for library authors, that want to provide a dark-light theme, without forcing the library implementor to use a specific theme provider, hooks etc. Or even colors.

Keeping colors agnostic to implementation, and easily allowing the user to use whatever they want, falling back to the default colors if they don't specify. If consumed by native apps, especially android most likely you will be already adapting to the defaults the application has set.

If you want to see a full example, check this snack, where you can see both approaches in action: https://snack.expo.dev/@filipef101/platformcolors-and-dynamic-colors

Final demo:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment