Custom Fonts In React Native

Lewis Barnes
Feb 21, 2019 · 6 min read

In this post I’d like to talk about some of the challenges I’ve faced in React Native when dealing with custom fonts.

In many apps, fonts play a big part in forming the brand’s identity and often means developers need to integrate custom fonts within the project. Unfortunately, this isn’t so easy in React Native, and I definitely think this topic should be covered in the official documentation.

Installing custom fonts

In order to use custom fonts, you will need to do some setup. For demonstration purposes, I’ll be adding the Merriweather Google font.

Note: If you’re here because you’d like to use icon fonts such as Font Awesome, MaterialIcons, Ionicons, etc, you might want to use react-native-vector-icons instead.

1. Update package.json

To get started, we need to update the package.json file to contain the following:

"rnpm": {
"assets": [
"./assets/fonts"
]
}

This will tell React Native that we would like to link the following asset directories to the project.

2. Copy font files

Let’s create the fonts directory and add our .tff font files:

mkdir -p ./assets/fonts
cp ~/Downloads/Merriweather/*.ttf ./assets/fonts

3. Align font filenames

It’s important that we align the font filenames to their respective PostScript names as this will be the value we reference in the app (more on this later). To make the process easier, below is a Node.js script that uses opentype.js to retrieve the PostScript name of each font file and renames any that are incorrect:

To execute the script, run the following:

npm i opentype.js
node renameFonts.js

4. Link font files

To integrate the font files into the project, we need to link the new assets for Android and iOS. The short answer is to run the following:

react-native link

However, I found that it can be frustrating if the project also integrates with other packages like react-native-vector-icons that rely on linking. With react-native-vector-icons, it’s common to cherry pick the icon set(s) you wish to use. Unfortunately, running the command above reintegrates all of its icon sets back into the project, leaving you to manually undo the changes each time. One solution is to use react-native-asset to link only the assets:

npx react-native-asset

For iOS, this will update the info.plist file with the files from the fonts directory and correctly link them to your project.

For Android, this will copy the files from the fonts directory into android/src/main/assets/fonts.

5. Compile

Because we’ve made changes to the native side, we will need to recompile the platforms:

react-native run-ios
react-native run-android

6. Verify Installation

To ensure the custom fonts are correctly installed, we’ll add a Text element for each of our custom font files in the app:

Should you wish to add more fonts in the future, simply follow from step 2.

Differences between iOS and Android

While there are a number of resources on the Web that cover how to install custom fonts, there are far less that talk about the nuances between platforms that make using custom fonts in React Native more complex than they may seem.

An issue I ran into early on with Android was that it silently reverts back to the system font if fontStyle or fontWeight styles are applied with a custom fontFamily. It wasn’t completely obvious at first as iOS doesn’t appear to have this problem and works as expected.

To avoid implementing a fix based on platform, the easiest solution is to conform with Android and only apply the fontFamily:

Unfortunately, we’re now left with another problem — How do we apply fontStyle and fontWeight now they are omitted from the style object?

To solve this problem, we need to dynamically apply the correct font family based on the font style and font weight. This is where the correct naming of the font files becomes useful:

You may have noticed that we no longer pass the full font family name to components, but instead specify the ‘base’ font name . This is because getFontFamily takes care of figuring out the suffix based on the font style and font weight.

Since Android silently defaults back to the system font if getFontFamily returns a invalid font family, I’d strongly recommend adding validation:

Nested Font Family Android Bug

I recently stumbled upon a bug with Android around nested Text using custom fonts. I did some experimenting and found that strangely nested Text would work fine when the parent Text font family was either the light or regular variation. Other variations for nested Text would revert back to the system font:

Given the issue has been open for over 6 months and the ongoing work to rewrite the codebase, it seemed pretty clear to me that it wouldn’t be fixed anytime soon, so I decided to implement an interim fix.

To understand what a fix might look like, let’s identify the four main use cases of Text:

  • Leaf node — props.children is not a component or an array.
  • Nested component(s) — props.children is a component or an array of components
  • String interpolation — props.children is a array that doesn’t contain components.
  • Mismatch of children — props.children is mixture of nested components and string interpolation.

It’s important that a solution to this problem is tackled in a way that doesn’t leak into the rest of the app. Doing so would mean introducing more complexity and ultimately increasing the chances of bugs. Additionally, should the bug be resolved in the future, it would be trivial to remove the patch as it’s centralised. Below is a solution that is encapsulated in a custom Text component, covering the four main use cases:

I’d like to highlight that in order to fix the last use case, leaf nodes in arrays are artificially wrapped in Text components as the font family can’t be applied to parent Text elements. Be aware that this may cause unexpected issues with styling, but I’ve not yet formed a comprehensive understanding of Text styling in React Native to provide enough insight. My speculation is that you may encounter layout differences.

My initial approach to this problem was to use a combination of React.cloneElement and React.Children.map. However, I found that while it worked for the use cases described above, it unveiled another issue. There’s no guarantee that directly nested components are Text. Though Text trees restrict that only Text based components can be rendered, it doesn’t guarantee that directly nested components aren’t simply Text wrapped in themed components (e.g. Heading). It’s therefore possible that these components may not accept additional styling and would cause headaches trying to track down why custom font aren’t working in particular cases:

<MyAppText
style={{
fontFamily: "Merriweather-Regular"
}}
>
{/* No guarantee that the style prop will be delegated to Heading’s rendered Text */}
<Heading>Hello World</Heading>
</MyAppText>

In my previous article, I talked about a concept I named Contextual Typography Styling. The fix shown above is a variation of that concept but isolated to just font styling. Below is an slightly altered version of the fix which caters for the final solution in that article:

Thanks for reading!

Lewis Barnes

Written by

Contract Software Engineer

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade