HomeGuidesReferenceLearn
ArchiveExpo SnackDiscord and ForumsNewsletter

Fonts

Learn about using custom fonts, supported font formats for each platform and loading them.


Both Android and iOS and most desktop operating systems come with their own set of platform fonts. However, if you want to inject some more brand personality into your app, a well-picked font can go a long way.

As each operating system has its own set of platform fonts, if you want to produce an experience that is consistent for all users, you'll want to use your fonts in your project. This page covers the aspects of getting a custom font, loading it in your project and what are some of the best practices to use when the font is being loaded in your project.

Get a font

The first thing you need is a font file. For a working example, we are going to use Inter Black from the free and open source Inter font family by Rasmus Anderson. A common convention in React Native apps is to put your fonts in an ./assets/fonts directory. However, you can put them anywhere you like.

Supported font formats

The two officially supported font formats that work consistently in the Expo SDK across Android, iOS and the web, are OTF and TTF. If your font is in another format, you will require to set up an advanced configuration for your project.

If you have both OTF and TTF versions of a font, prefer OTF. OTF is a newer format and .otf files are often smaller than .ttf files. Sometimes OTF files render slightly better in certain contexts. In general, both formats are very similar and perfectly acceptable.

Beyond OTF and TTF

If your font is in another format, you have to customize the Metro bundler configuration to get anything other than OTF and TTF to work. In some cases, trying to render a font format that a platform doesn't support may cause your app to crash.

For reference, the following table provides what formats work on which platforms:

FormatWebiOSAndroid
bdf
dfont
eot
fon
otf
ps
svg
ttc
ttf
woff
woff2

Use a custom font

There are two ways to use a custom font in your project: either embed the font in your native project or load it at runtime. The first approach is recommended as it is simpler and more reliable. The second approach is useful if you want to load the font in Expo Go/without creating a new native build that includes it or loading it from a remote URL.

Embed font in a native project

Note: The expo-font config plugin is only available for SDK 50 and above. If you are using an older SDK, you can load the font at runtime instead.

Install expo-font library and add the config plugin to your app config file. The plugin will embed the font in your native project.

app.json
{
  "expo": {
    "plugins": [
      [
        "expo-font",
        {
          "fonts": ["./assets/fonts/Inter-Black.otf"]
        }
      ]
    ]
  }
}

The fonts option takes an array of one or more font files to link to the native project. The path to each font file is relative to the project's root.

After creating a new native build, you can use the font in your project with the fontFamily style prop. On Android, the font family name will be the name of the font file without the extension. On iOS, the font family name will be read from the font file itself. We recommend naming the font file the same as the PostScript name so the family name is consistent on both platforms. For example, if you have a font family named "Inter-Black" and the name of the file Inter-Black.otf, then the font family name will be Inter-Black on both Android and iOS.

If you want to try a font without creating a new native build, you can load it at runtime. See the next section for details.

Are you using this library in a bare React Native app?
  • Android: Copy font files to android/app/src/main/assets/fonts.
  • iOS: See Adding a Custom Font to Your App in the Apple Developer documentation.

Load font at runtime

After getting the font file, in your project, you need to install expo-font library.

Import the font

After the installation step, import the useFonts hook from expo-font package in your project. The hook keeps track of the loading state of the font. When an app is initialized, the hook loads the map of fonts as shown in the example below:

App.js
// Rest of the import statements
import { useFonts } from 'expo-font';

export default function App() {
  const [fontsLoaded] = useFonts({
    'Inter-Black': require('./assets/fonts/Inter-Black.otf'),
  });
}

Then, you can use the font on the <Text> by using fontFamily style prop.

<Text style={{ fontFamily: 'Inter-Black', fontSize: 30 }}>Inter Black</Text>

Alternatively, you can use Font.loadAsync to load the fonts in your app.

Minimal example

Let's take a look at a minimal example that uses Inter font family. It uses useFonts hook to import the font from ./assets/fonts directory.

Using custom fonts
import { useCallback } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { useFonts } from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';

SplashScreen.preventAutoHideAsync();

export default function App() {
  const [fontsLoaded, fontError] = useFonts({
    'Inter-Black': require('./assets/fonts/Inter-Black.otf'),
  });

  const onLayoutRootView = useCallback(async () => {
    if (fontsLoaded || fontError) {
      await SplashScreen.hideAsync();
    }
  }, [fontsLoaded, fontError]);

  if (!fontsLoaded && !fontError) {
    return null;
  }

  return (
    <View style={styles.container} onLayout={onLayoutRootView}>
      <Text style={{ fontFamily: 'Inter-Black', fontSize: 30 }}>Inter Black</Text>
      <Text style={{ fontSize: 30 }}>Platform Default</Text>
    </View>
  );
}

%%placeholder-start%%const styles = StyleSheet.create({ ... }); %%placeholder-end%%const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

Inter Black is very bold and dark and pretty distinctive so you should be able to tell if you're able to get the example working right, or if something is wrong. If the platform default font looks a little different for you, that's fine; the platform default font can vary depending on the operating system and the device manufacturer (on Android).

When you load it on your device, you'll see something like this:

To create a new project including this example, run in your terminal:

Terminal
- npx create-expo-app --example with-custom-font

The above example also uses expo-splash-screen package. For more information on that, see Waiting for fonts to load section.

Platform built-in fonts

If you don't want to use a custom font, you can use the platform's default font by not specifying a font family. Each platform has a different set of fonts available by default, so there's no good way to specify one that will work everywhere without supplying your custom font.

On the web, there are several generic font families that you can specify. Different browsers and operating systems are configured to use different fonts for each of these font family specifications. For example, Safari on an iPhone uses San Francisco as its default for sans-serif while Microsoft Edge on Windows uses Arial. Similarly, Chrome on Android uses Roboto, though OnePlus phones often use Slate, and so on.

  • sans-serif
  • serif
  • monospace
  • fantasy
  • cursive

In general, your safest bets are just to use the system default which usually is an easy-to-read sans-serif font that the user of any system should be familiar with. However, don't be surprised when the system default font is changed to use another font that is not easy to read but at the same time, is supported on the platform or the device. In this case, use your custom font so you have precise control over what the user will see.

Use a Google Font

Expo has first-class support for all fonts listed in Google Fonts. To use one of these, check out the expo-google-fonts package. With these packages, you can quickly integrate any font or font variants.

For example, to use Inter font you can install the @expo-google-fonts/inter package with the command below.

Terminal
- npx expo install expo-font @expo-google-fonts/inter

To use it in your project, you can embed it in your project with the config plugin or use the useFonts hook.

With config plugin

To use this approach, you need to create a new native build that includes the font. It is done by passing the path to the desired font file to the expo-fonts.fonts option. An example is shown below:

app.json
{
  "plugins": [
    [
      "expo-font",
      {
        "fonts": ["node_modules/@expo-google-fonts/inter/Inter_100Thin.ttf"]
      }
    ]
  ]
}

If the font you are using includes multiple weights (for example, Inter_100Thin, Inter_700Bold and so on), you can directly use the font file name for Android. For iOS, use the font and its weight name.

The example below demonstrates how to use Platform to select the correct font family name for each platform:

import { Platform } from 'react-native';

// Inside a component:
<Text
  style={{
    fontFamily: Platform.select({
      android: 'Inter_100Thin',
      ios: 'Inter-Thin',
    })
  }}>
  Inter Bold
</Text>

useFonts hook

Each Google Fonts package provides the useFonts hook for convenience. Under the hood, the hook uses Font.loadAsync. You do not have to explicitly import the font file since the package itself does that.

Using Google fonts
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useFonts, Inter_900Black } from '@expo-google-fonts/inter';

export default function App() {
  let [fontsLoaded, fontError] = useFonts({
    Inter_900Black,
  });

  if (!fontsLoaded && !fontError) {
    return null;
  }

  return (
    <View style={styles.container}>
      <Text style={{ fontFamily: 'Inter_900Black', fontSize: 40 }}>Inter Black</Text>
    </View>
  );
}

%%placeholder-start%%const styles = StyleSheet.create({ ... }); %%placeholder-end%%const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

Wait for fonts to load

If you embed your fonts in your native project, they will be available immediately without any additional code outside the config plugin. However, if you load your fonts at runtime, they will not be available immediately. So, it is generally a good practice not to render anything until the font is ready. Instead, you can continue to display the Splash Screen of your app until all fonts have loaded (or an error has been returned). It is done using expo-splash-screen package. See the minimal example section on how to use it.

Load fonts on the web

Sometimes, particularly on the web people choose to render their content in a platform default font while their custom font is loading. Alternatively, to render the rest of their content, that doesn't depend on the custom font while the font is loading. These approaches are called FOUT and FOIT and you can read a lot more about them on the web.

In general, these strategies are not recommended for native apps. If you include your fonts in your project, the fonts will always be delivered to the user by the time your code is running. The one exception to this is that you may prefer to do this on the web.

Additional information

You probably don't need to know anything beyond this point to use custom fonts effectively in your app. If you are curious or your use case has not been addressed by the above information, please continue reading.

Loading a remote font directly from the web

In general, it's best and safest to load fonts from your local assets. If you submit to app stores, they will be bundled with the download and available immediately. You don't have to worry about CORS or other potential issues.

However, if you to load a remote font file directly from the web rather than from your project's assets, you can do it by replacing the require('./assets/fonts/MyFont.otf') with the URL of your font. See the below example:

Using a remote font
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { useFonts } from 'expo-font';

export default function App() {
  const [fontsLoaded] = useFonts({
    'Inter-SemiBoldItalic': 'https://rsms.me/inter/font-files/Inter-SemiBoldItalic.otf?v=3.12',
  });

  if (!fontsLoaded) {
    return null;
  }

  return (
    <View style={styles.container}>
      <Text style={{ fontFamily: 'Inter-SemiBoldItalic', fontSize: 30 }}>Inter SemiBoldItalic</Text>
      <Text style={{ fontSize: 30 }}>Platform Default</Text>
    </View>
  );
}
%%placeholder-start%%const styles = StyleSheet.create({ ... }); %%placeholder-end%%const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});
If loading remote fonts, make sure they are being served from an origin with CORS properly configured. If you don't do this, your remote font might not load properly on the web platform.

Using Font.loadAsync instead of the useFonts hook

If you don't want to use the useFonts hook (for example, maybe you prefer class components), you can use Font.loadAsync directly. Under the hood, the hook uses Font.loadAsync from the expo-font library. You can use it directly if you prefer, or if you want to have more fine-grained control over when your fonts are loaded before rendering.

Loading font async
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import * as Font from 'expo-font';

let customFonts = {
  'Inter-Black': require('./assets/fonts/Inter-Black.otf'),
  'Inter-SemiBoldItalic': 'https://rsms.me/inter/font-files/Inter-SemiBoldItalic.otf?v=3.12',
};

export default class App extends React.Component {
  state = {
    fontsLoaded: false,
  };

  async _loadFontsAsync() {
    await Font.loadAsync(customFonts);
    this.setState({ fontsLoaded: true });
  }

  componentDidMount() {
    this._loadFontsAsync();
  }

  render() {
    if (!this.state.fontsLoaded) {
      return null;
    }

    return (
      <View style={styles.container}>
        <Text style={{ fontFamily: 'Inter-Black', fontSize: 30 }}>Inter Black</Text>
        <Text style={{ fontFamily: 'Inter-SemiBoldItalic', fontSize: 30 }}>
          Inter SemiBoldItalic
        </Text>
        <Text style={{ fontSize: 30 }}>Platform Default</Text>
      </View>
    );
  }
}

%%placeholder-start%%const styles = StyleSheet.create({ ... }); %%placeholder-end%%const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});